notionary 0.2.19__py3-none-any.whl → 0.2.22__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- notionary/__init__.py +8 -4
- notionary/base_notion_client.py +3 -1
- notionary/blocks/__init__.py +2 -91
- notionary/blocks/_bootstrap.py +271 -0
- notionary/blocks/audio/__init__.py +8 -2
- notionary/blocks/audio/audio_element.py +69 -106
- notionary/blocks/audio/audio_markdown_node.py +13 -5
- notionary/blocks/audio/audio_models.py +6 -55
- notionary/blocks/base_block_element.py +42 -0
- notionary/blocks/bookmark/__init__.py +9 -2
- notionary/blocks/bookmark/bookmark_element.py +49 -139
- notionary/blocks/bookmark/bookmark_markdown_node.py +19 -18
- notionary/blocks/bookmark/bookmark_models.py +15 -0
- notionary/blocks/breadcrumbs/__init__.py +17 -0
- notionary/blocks/breadcrumbs/breadcrumb_element.py +39 -0
- notionary/blocks/breadcrumbs/breadcrumb_markdown_node.py +32 -0
- notionary/blocks/breadcrumbs/breadcrumb_models.py +12 -0
- notionary/blocks/bulleted_list/__init__.py +12 -2
- notionary/blocks/bulleted_list/bulleted_list_element.py +55 -53
- notionary/blocks/bulleted_list/bulleted_list_markdown_node.py +2 -1
- notionary/blocks/bulleted_list/bulleted_list_models.py +18 -0
- notionary/blocks/callout/__init__.py +9 -2
- notionary/blocks/callout/callout_element.py +53 -86
- notionary/blocks/callout/callout_markdown_node.py +3 -1
- notionary/blocks/callout/callout_models.py +33 -0
- notionary/blocks/child_database/__init__.py +14 -0
- notionary/blocks/child_database/child_database_element.py +61 -0
- notionary/blocks/child_database/child_database_models.py +12 -0
- notionary/blocks/child_page/__init__.py +9 -0
- notionary/blocks/child_page/child_page_element.py +94 -0
- notionary/blocks/child_page/child_page_models.py +12 -0
- notionary/blocks/{shared/block_client.py → client.py} +54 -54
- notionary/blocks/code/__init__.py +6 -2
- notionary/blocks/code/code_element.py +96 -181
- notionary/blocks/code/code_markdown_node.py +64 -13
- notionary/blocks/code/code_models.py +94 -0
- notionary/blocks/column/__init__.py +25 -1
- notionary/blocks/column/column_element.py +44 -312
- notionary/blocks/column/column_list_element.py +52 -0
- notionary/blocks/column/column_list_markdown_node.py +50 -0
- notionary/blocks/column/column_markdown_node.py +59 -0
- notionary/blocks/column/column_models.py +26 -0
- notionary/blocks/divider/__init__.py +9 -2
- notionary/blocks/divider/divider_element.py +18 -49
- notionary/blocks/divider/divider_markdown_node.py +2 -1
- notionary/blocks/divider/divider_models.py +12 -0
- notionary/blocks/embed/__init__.py +9 -2
- notionary/blocks/embed/embed_element.py +65 -111
- notionary/blocks/embed/embed_markdown_node.py +3 -1
- notionary/blocks/embed/embed_models.py +14 -0
- notionary/blocks/equation/__init__.py +14 -0
- notionary/blocks/equation/equation_element.py +133 -0
- notionary/blocks/equation/equation_element_markdown_node.py +35 -0
- notionary/blocks/equation/equation_models.py +11 -0
- notionary/blocks/file/__init__.py +25 -0
- notionary/blocks/file/file_element.py +112 -0
- notionary/blocks/file/file_element_markdown_node.py +37 -0
- notionary/blocks/file/file_element_models.py +39 -0
- notionary/blocks/guards.py +22 -0
- notionary/blocks/heading/__init__.py +16 -2
- notionary/blocks/heading/heading_element.py +83 -69
- notionary/blocks/heading/heading_markdown_node.py +2 -1
- notionary/blocks/heading/heading_models.py +29 -0
- notionary/blocks/image_block/__init__.py +13 -0
- notionary/blocks/image_block/image_element.py +89 -0
- notionary/blocks/{image → image_block}/image_markdown_node.py +13 -6
- notionary/blocks/image_block/image_models.py +10 -0
- notionary/blocks/mixins/captions/__init__.py +4 -0
- notionary/blocks/mixins/captions/caption_markdown_node_mixin.py +31 -0
- notionary/blocks/mixins/captions/caption_mixin.py +92 -0
- notionary/blocks/models.py +174 -0
- notionary/blocks/numbered_list/__init__.py +12 -2
- notionary/blocks/numbered_list/numbered_list_element.py +48 -56
- notionary/blocks/numbered_list/numbered_list_markdown_node.py +3 -1
- notionary/blocks/numbered_list/numbered_list_models.py +17 -0
- notionary/blocks/paragraph/__init__.py +12 -2
- notionary/blocks/paragraph/paragraph_element.py +40 -66
- notionary/blocks/paragraph/paragraph_markdown_node.py +2 -1
- notionary/blocks/paragraph/paragraph_models.py +16 -0
- notionary/blocks/pdf/__init__.py +13 -0
- notionary/blocks/pdf/pdf_element.py +97 -0
- notionary/blocks/pdf/pdf_markdown_node.py +37 -0
- notionary/blocks/pdf/pdf_models.py +11 -0
- notionary/blocks/quote/__init__.py +11 -2
- notionary/blocks/quote/quote_element.py +45 -62
- notionary/blocks/quote/quote_markdown_node.py +6 -3
- notionary/blocks/quote/quote_models.py +18 -0
- notionary/blocks/registry/__init__.py +4 -0
- notionary/blocks/registry/block_registry.py +60 -121
- notionary/blocks/registry/block_registry_builder.py +115 -59
- notionary/blocks/rich_text/__init__.py +33 -0
- notionary/blocks/rich_text/name_to_id_resolver.py +205 -0
- notionary/blocks/rich_text/rich_text_models.py +221 -0
- notionary/blocks/rich_text/text_inline_formatter.py +456 -0
- notionary/blocks/syntax_prompt_builder.py +137 -0
- notionary/blocks/table/__init__.py +16 -2
- notionary/blocks/table/table_element.py +136 -228
- notionary/blocks/table/table_markdown_node.py +2 -1
- notionary/blocks/table/table_models.py +28 -0
- notionary/blocks/table_of_contents/__init__.py +19 -0
- notionary/blocks/table_of_contents/table_of_contents_element.py +68 -0
- notionary/blocks/table_of_contents/table_of_contents_markdown_node.py +35 -0
- notionary/blocks/table_of_contents/table_of_contents_models.py +18 -0
- notionary/blocks/todo/__init__.py +9 -2
- notionary/blocks/todo/todo_element.py +52 -92
- notionary/blocks/todo/todo_markdown_node.py +2 -1
- notionary/blocks/todo/todo_models.py +19 -0
- notionary/blocks/toggle/__init__.py +13 -3
- notionary/blocks/toggle/toggle_element.py +69 -260
- notionary/blocks/toggle/toggle_markdown_node.py +25 -15
- notionary/blocks/toggle/toggle_models.py +17 -0
- notionary/blocks/toggleable_heading/__init__.py +6 -2
- notionary/blocks/toggleable_heading/toggleable_heading_element.py +86 -241
- notionary/blocks/toggleable_heading/toggleable_heading_markdown_node.py +26 -18
- notionary/blocks/types.py +130 -0
- notionary/blocks/video/__init__.py +8 -2
- notionary/blocks/video/video_element.py +70 -141
- notionary/blocks/video/video_element_models.py +10 -0
- notionary/blocks/video/video_markdown_node.py +13 -6
- notionary/database/client.py +26 -8
- notionary/database/database.py +13 -14
- notionary/database/database_filter_builder.py +2 -2
- notionary/database/database_provider.py +5 -4
- notionary/database/models.py +337 -0
- notionary/database/notion_database.py +6 -7
- notionary/file_upload/client.py +5 -7
- notionary/file_upload/models.py +3 -2
- notionary/file_upload/notion_file_upload.py +2 -3
- notionary/markdown/markdown_builder.py +729 -0
- notionary/markdown/markdown_document_model.py +228 -0
- notionary/{blocks → markdown}/markdown_node.py +1 -0
- notionary/models/notion_database_response.py +0 -338
- notionary/page/client.py +34 -15
- notionary/page/models.py +327 -0
- notionary/page/notion_page.py +136 -58
- notionary/page/{content/page_content_writer.py → page_content_deleting_service.py} +25 -59
- notionary/page/page_content_writer.py +177 -0
- notionary/page/page_context.py +65 -0
- notionary/page/reader/handler/__init__.py +19 -0
- notionary/page/reader/handler/base_block_renderer.py +44 -0
- notionary/page/reader/handler/block_processing_context.py +35 -0
- notionary/page/reader/handler/block_rendering_context.py +48 -0
- notionary/page/reader/handler/column_list_renderer.py +51 -0
- notionary/page/reader/handler/column_renderer.py +60 -0
- notionary/page/reader/handler/line_renderer.py +73 -0
- notionary/page/reader/handler/numbered_list_renderer.py +85 -0
- notionary/page/reader/handler/toggle_renderer.py +69 -0
- notionary/page/reader/handler/toggleable_heading_renderer.py +89 -0
- notionary/page/reader/page_content_retriever.py +81 -0
- notionary/page/search_filter_builder.py +2 -1
- notionary/page/writer/handler/__init__.py +24 -0
- notionary/page/writer/handler/code_handler.py +72 -0
- notionary/page/writer/handler/column_handler.py +141 -0
- notionary/page/writer/handler/column_list_handler.py +139 -0
- notionary/page/writer/handler/equation_handler.py +74 -0
- notionary/page/writer/handler/line_handler.py +35 -0
- notionary/page/writer/handler/line_processing_context.py +54 -0
- notionary/page/writer/handler/regular_line_handler.py +86 -0
- notionary/page/writer/handler/table_handler.py +66 -0
- notionary/page/writer/handler/toggle_handler.py +155 -0
- notionary/page/writer/handler/toggleable_heading_handler.py +173 -0
- notionary/page/writer/markdown_to_notion_converter.py +95 -0
- notionary/page/writer/markdown_to_notion_converter_context.py +30 -0
- notionary/page/writer/markdown_to_notion_formatting_post_processor.py +73 -0
- notionary/page/writer/notion_text_length_processor.py +150 -0
- notionary/telemetry/__init__.py +2 -2
- notionary/telemetry/service.py +3 -3
- notionary/user/__init__.py +2 -2
- notionary/user/base_notion_user.py +2 -1
- notionary/user/client.py +2 -3
- notionary/user/models.py +1 -0
- notionary/user/notion_bot_user.py +4 -5
- notionary/user/notion_user.py +3 -4
- notionary/user/notion_user_manager.py +23 -95
- notionary/util/__init__.py +3 -2
- notionary/util/fuzzy.py +2 -1
- notionary/util/logging_mixin.py +2 -2
- notionary/util/singleton_metaclass.py +1 -1
- notionary/workspace.py +6 -5
- notionary-0.2.22.dist-info/METADATA +237 -0
- notionary-0.2.22.dist-info/RECORD +200 -0
- notionary/blocks/document/__init__.py +0 -7
- notionary/blocks/document/document_element.py +0 -102
- notionary/blocks/document/document_markdown_node.py +0 -31
- notionary/blocks/image/__init__.py +0 -7
- notionary/blocks/image/image_element.py +0 -151
- notionary/blocks/markdown_builder.py +0 -356
- notionary/blocks/mention/__init__.py +0 -7
- notionary/blocks/mention/mention_element.py +0 -229
- notionary/blocks/mention/mention_markdown_node.py +0 -38
- notionary/blocks/prompts/element_prompt_builder.py +0 -83
- notionary/blocks/prompts/element_prompt_content.py +0 -41
- notionary/blocks/shared/models.py +0 -713
- notionary/blocks/shared/notion_block_element.py +0 -37
- notionary/blocks/shared/text_inline_formatter.py +0 -262
- notionary/blocks/shared/text_inline_formatter_new.py +0 -139
- notionary/database/models/page_result.py +0 -10
- notionary/models/notion_block_response.py +0 -264
- notionary/models/notion_page_response.py +0 -78
- notionary/models/search_response.py +0 -0
- notionary/page/__init__.py +0 -0
- notionary/page/content/markdown_whitespace_processor.py +0 -80
- notionary/page/content/notion_text_length_utils.py +0 -87
- notionary/page/content/page_content_retriever.py +0 -60
- notionary/page/formatting/line_processor.py +0 -153
- notionary/page/formatting/markdown_to_notion_converter.py +0 -153
- notionary/page/markdown_syntax_prompt_generator.py +0 -114
- notionary/page/notion_to_markdown_converter.py +0 -179
- notionary/page/properites/property_value_extractor.py +0 -0
- notionary/user/notion_user_provider.py +0 -1
- notionary-0.2.19.dist-info/METADATA +0 -225
- notionary-0.2.19.dist-info/RECORD +0 -150
- /notionary/{blocks/document/document_models.py → markdown/___init__.py} +0 -0
- /notionary/{blocks/image/image_models.py → markdown/makdown_document_model.py} +0 -0
- /notionary/{blocks/mention/mention_models.py → page/reader/handler/equation_renderer.py} +0 -0
- /notionary/{blocks/shared/__init__.py → page/writer/markdown_to_notion_post_processor.py} +0 -0
- /notionary/{blocks/toggleable_heading/toggleable_heading_models.py → page/writer/markdown_to_notion_text_length_post_processor.py} +0 -0
- /notionary/{elements/__init__.py → util/concurrency_limiter.py} +0 -0
- {notionary-0.2.19.dist-info → notionary-0.2.22.dist-info}/LICENSE +0 -0
- {notionary-0.2.19.dist-info → notionary-0.2.22.dist-info}/WHEEL +0 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
from typing import Literal
|
3
|
+
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field
|
5
|
+
|
6
|
+
from notionary.blocks.rich_text.rich_text_models import RichTextObject
|
7
|
+
|
8
|
+
|
9
|
+
class CodeLanguage(str, Enum):
|
10
|
+
ABAP = "abap"
|
11
|
+
ARDUINO = "arduino"
|
12
|
+
BASH = "bash"
|
13
|
+
BASIC = "basic"
|
14
|
+
C = "c"
|
15
|
+
CLOJURE = "clojure"
|
16
|
+
COFFEESCRIPT = "coffeescript"
|
17
|
+
CPP = "c++"
|
18
|
+
CSHARP = "c#"
|
19
|
+
CSS = "css"
|
20
|
+
DART = "dart"
|
21
|
+
DIFF = "diff"
|
22
|
+
DOCKER = "docker"
|
23
|
+
ELIXIR = "elixir"
|
24
|
+
ELM = "elm"
|
25
|
+
ERLANG = "erlang"
|
26
|
+
FLOW = "flow"
|
27
|
+
FORTRAN = "fortran"
|
28
|
+
FSHARP = "f#"
|
29
|
+
GHERKIN = "gherkin"
|
30
|
+
GLSL = "glsl"
|
31
|
+
GO = "go"
|
32
|
+
GRAPHQL = "graphql"
|
33
|
+
GROOVY = "groovy"
|
34
|
+
HASKELL = "haskell"
|
35
|
+
HTML = "html"
|
36
|
+
JAVA = "java"
|
37
|
+
JAVASCRIPT = "javascript"
|
38
|
+
JSON = "json"
|
39
|
+
JULIA = "julia"
|
40
|
+
KOTLIN = "kotlin"
|
41
|
+
LATEX = "latex"
|
42
|
+
LESS = "less"
|
43
|
+
LISP = "lisp"
|
44
|
+
LIVESCRIPT = "livescript"
|
45
|
+
LUA = "lua"
|
46
|
+
MAKEFILE = "makefile"
|
47
|
+
MARKDOWN = "markdown"
|
48
|
+
MARKUP = "markup"
|
49
|
+
MATLAB = "matlab"
|
50
|
+
MERMAID = "mermaid"
|
51
|
+
NIX = "nix"
|
52
|
+
OBJECTIVE_C = "objective-c"
|
53
|
+
OCAML = "ocaml"
|
54
|
+
PASCAL = "pascal"
|
55
|
+
PERL = "perl"
|
56
|
+
PHP = "php"
|
57
|
+
PLAIN_TEXT = "plain text"
|
58
|
+
POWERSHELL = "powershell"
|
59
|
+
PROLOG = "prolog"
|
60
|
+
PROTOBUF = "protobuf"
|
61
|
+
PYTHON = "python"
|
62
|
+
R = "r"
|
63
|
+
REASON = "reason"
|
64
|
+
RUBY = "ruby"
|
65
|
+
RUST = "rust"
|
66
|
+
SASS = "sass"
|
67
|
+
SCALA = "scala"
|
68
|
+
SCHEME = "scheme"
|
69
|
+
SCSS = "scss"
|
70
|
+
SHELL = "shell"
|
71
|
+
SQL = "sql"
|
72
|
+
SWIFT = "swift"
|
73
|
+
TYPESCRIPT = "typescript"
|
74
|
+
VB_NET = "vb.net"
|
75
|
+
VERILOG = "verilog"
|
76
|
+
VHDL = "vhdl"
|
77
|
+
VISUAL_BASIC = "visual basic"
|
78
|
+
WEBASSEMBLY = "webassembly"
|
79
|
+
XML = "xml"
|
80
|
+
YAML = "yaml"
|
81
|
+
JAVA_C_CPP_CSHARP = "java/c/c++/c#"
|
82
|
+
|
83
|
+
|
84
|
+
class CodeBlock(BaseModel):
|
85
|
+
caption: list[RichTextObject] = Field(default_factory=list)
|
86
|
+
rich_text: list[RichTextObject]
|
87
|
+
language: CodeLanguage = CodeLanguage.PLAIN_TEXT
|
88
|
+
|
89
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
90
|
+
|
91
|
+
|
92
|
+
class CreateCodeBlock(BaseModel):
|
93
|
+
type: Literal["code"] = "code"
|
94
|
+
code: CodeBlock
|
@@ -1,5 +1,29 @@
|
|
1
|
-
from .column_element import ColumnElement
|
1
|
+
from notionary.blocks.column.column_element import ColumnElement
|
2
|
+
from notionary.blocks.column.column_list_element import ColumnListElement
|
3
|
+
from notionary.blocks.column.column_list_markdown_node import (
|
4
|
+
ColumnListMarkdownBlockParams,
|
5
|
+
ColumnListMarkdownNode,
|
6
|
+
)
|
7
|
+
from notionary.blocks.column.column_markdown_node import (
|
8
|
+
ColumnMarkdownBlockParams,
|
9
|
+
ColumnMarkdownNode,
|
10
|
+
)
|
11
|
+
from notionary.blocks.column.column_models import (
|
12
|
+
ColumnBlock,
|
13
|
+
ColumnListBlock,
|
14
|
+
CreateColumnBlock,
|
15
|
+
CreateColumnListBlock,
|
16
|
+
)
|
2
17
|
|
3
18
|
__all__ = [
|
4
19
|
"ColumnElement",
|
20
|
+
"ColumnListElement",
|
21
|
+
"ColumnBlock",
|
22
|
+
"CreateColumnBlock",
|
23
|
+
"ColumnListBlock",
|
24
|
+
"CreateColumnListBlock",
|
25
|
+
"ColumnMarkdownNode",
|
26
|
+
"ColumnMarkdownBlockParams",
|
27
|
+
"ColumnListMarkdownNode",
|
28
|
+
"ColumnListMarkdownBlockParams",
|
5
29
|
]
|
@@ -1,333 +1,65 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import re
|
2
|
-
from typing import Optional
|
4
|
+
from typing import Optional
|
3
5
|
|
4
|
-
from notionary.blocks import
|
5
|
-
from notionary.blocks import
|
6
|
-
|
7
|
-
|
8
|
-
NotionBlockResult,
|
9
|
-
)
|
6
|
+
from notionary.blocks.base_block_element import BaseBlockElement
|
7
|
+
from notionary.blocks.column.column_models import ColumnBlock, CreateColumnBlock
|
8
|
+
from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformation
|
9
|
+
from notionary.blocks.models import Block, BlockCreateResult, BlockType
|
10
10
|
|
11
11
|
|
12
|
-
class ColumnElement(
|
12
|
+
class ColumnElement(BaseBlockElement):
|
13
13
|
"""
|
14
|
-
Handles
|
15
|
-
|
16
|
-
Markdown column syntax:
|
17
|
-
::: columns
|
18
|
-
::: column
|
19
|
-
Content for first column
|
20
|
-
:::
|
21
|
-
::: column
|
22
|
-
Content for second column
|
23
|
-
:::
|
24
|
-
:::
|
14
|
+
Handles individual `::: column` blocks with optional width ratio.
|
15
|
+
Content is automatically added by the stack processor.
|
25
16
|
|
26
|
-
|
17
|
+
Supported syntax:
|
18
|
+
- `::: column` (equal width)
|
19
|
+
- `::: column 0.5` (50% width)
|
20
|
+
- `::: column 0.25` (25% width)
|
27
21
|
"""
|
28
22
|
|
29
|
-
|
30
|
-
COLUMN_START = re.compile(r"^:::\s*column\s*$")
|
31
|
-
BLOCK_END = re.compile(r"^:::\s*$")
|
32
|
-
|
33
|
-
_converter_callback = None
|
23
|
+
COLUMN_START = re.compile(r"^:::\s*column(?:\s+(0?\.\d+|1\.0?))?\s*$")
|
34
24
|
|
35
25
|
@classmethod
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
"""
|
40
|
-
Setze die Callback-Funktion, die zum Konvertieren von Markdown zu Notion-Blöcken verwendet wird.
|
41
|
-
|
42
|
-
Args:
|
43
|
-
callback: Funktion, die Markdown-Text annimmt und eine Liste von Notion-Blöcken zurückgibt
|
44
|
-
"""
|
45
|
-
cls._converter_callback = callback
|
46
|
-
|
47
|
-
@staticmethod
|
48
|
-
def match_markdown(text: str) -> bool:
|
49
|
-
"""Check if text starts a columns block."""
|
50
|
-
return bool(ColumnElement.COLUMNS_START.match(text.strip()))
|
51
|
-
|
52
|
-
@staticmethod
|
53
|
-
def match_notion(block: dict[str, any]) -> bool:
|
54
|
-
"""Check if block is a Notion column_list."""
|
55
|
-
return block.get("type") == "column_list"
|
56
|
-
|
57
|
-
@staticmethod
|
58
|
-
def markdown_to_notion(text: str) -> NotionBlockResult:
|
59
|
-
"""
|
60
|
-
Convert markdown column syntax to Notion column blocks.
|
61
|
-
|
62
|
-
Note: This only processes the first line (columns start).
|
63
|
-
The full column content needs to be processed separately.
|
64
|
-
"""
|
65
|
-
if not ColumnElement.COLUMNS_START.match(text.strip()):
|
66
|
-
return None
|
67
|
-
|
68
|
-
# Create an empty column_list block
|
69
|
-
# Child columns will be added by the column processor
|
70
|
-
return [{"type": "column_list", "column_list": {"children": []}}]
|
71
|
-
|
72
|
-
@staticmethod
|
73
|
-
def notion_to_markdown(block: dict[str, any]) -> Optional[str]:
|
74
|
-
"""Convert Notion column_list block to markdown column syntax."""
|
75
|
-
if block.get("type") != "column_list":
|
76
|
-
return None
|
77
|
-
|
78
|
-
column_children = block.get("column_list", {}).get("children", [])
|
79
|
-
|
80
|
-
# Start the columns block
|
81
|
-
result = ["::: columns"]
|
82
|
-
|
83
|
-
# Process each column
|
84
|
-
for column_block in column_children:
|
85
|
-
if column_block.get("type") == "column":
|
86
|
-
result.append("::: column")
|
87
|
-
|
88
|
-
for _ in column_block.get("column", {}).get("children", []):
|
89
|
-
result.append(" [Column content]") # Placeholder
|
90
|
-
|
91
|
-
result.append(":::")
|
92
|
-
|
93
|
-
# End the columns block
|
94
|
-
result.append(":::")
|
95
|
-
|
96
|
-
return "\n".join(result)
|
97
|
-
|
98
|
-
@staticmethod
|
99
|
-
def is_multiline() -> bool:
|
100
|
-
"""Column blocks span multiple lines."""
|
101
|
-
return True
|
102
|
-
|
103
|
-
@classmethod
|
104
|
-
def find_matches(
|
105
|
-
cls, text: str, converter_callback: Optional[Callable] = None
|
106
|
-
) -> list[tuple[int, int, dict[str, any]]]:
|
107
|
-
"""
|
108
|
-
Find all column block matches in the text and return their positions and blocks.
|
109
|
-
|
110
|
-
Args:
|
111
|
-
text: The input markdown text
|
112
|
-
converter_callback: Optional callback to convert nested content
|
113
|
-
|
114
|
-
Returns:
|
115
|
-
List of tuples (start_pos, end_pos, block)
|
116
|
-
"""
|
117
|
-
# Wenn ein Callback übergeben wurde, nutze diesen, sonst die gespeicherte Referenz
|
118
|
-
converter = converter_callback or cls._converter_callback
|
119
|
-
if not converter:
|
120
|
-
raise ValueError(
|
121
|
-
"No converter callback provided for ColumnElement. Call set_converter_callback first or provide converter_callback parameter."
|
122
|
-
)
|
123
|
-
|
124
|
-
matches = []
|
125
|
-
lines = text.split("\n")
|
126
|
-
i = 0
|
127
|
-
|
128
|
-
while i < len(lines):
|
129
|
-
# Skip non-column lines
|
130
|
-
if not ColumnElement.COLUMNS_START.match(lines[i].strip()):
|
131
|
-
i += 1
|
132
|
-
continue
|
133
|
-
|
134
|
-
# Process a column block and add to matches
|
135
|
-
column_block_info = cls._process_column_block(
|
136
|
-
lines=lines, start_index=i, converter_callback=converter
|
137
|
-
)
|
138
|
-
matches.append(column_block_info)
|
139
|
-
|
140
|
-
# Skip to the end of the processed column block
|
141
|
-
i = column_block_info[3] # i is returned as the 4th element in the tuple
|
142
|
-
|
143
|
-
return [(start, end, block) for start, end, block, _ in matches]
|
26
|
+
def match_notion(cls, block: Block) -> bool:
|
27
|
+
"""Check if block is a Notion column."""
|
28
|
+
return block.type == BlockType.COLUMN and block.column
|
144
29
|
|
145
30
|
@classmethod
|
146
|
-
def
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
Process a complete column block structure from the given starting line.
|
151
|
-
|
152
|
-
Args:
|
153
|
-
lines: All lines of the text
|
154
|
-
start_index: Index of the column block start line
|
155
|
-
converter_callback: Callback function to convert markdown to notion blocks
|
156
|
-
|
157
|
-
Returns:
|
158
|
-
Tuple of (start_pos, end_pos, block, next_line_index)
|
159
|
-
"""
|
160
|
-
columns_start = start_index
|
161
|
-
columns_blocks = cls.markdown_to_notion(lines[start_index].strip())
|
162
|
-
columns_block = columns_blocks[0] if columns_blocks else None
|
163
|
-
columns_children = []
|
164
|
-
|
165
|
-
next_index = cls._collect_columns(
|
166
|
-
lines, start_index + 1, columns_children, converter_callback
|
167
|
-
)
|
31
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
32
|
+
"""Convert `::: column [ratio]` to Notion ColumnBlock."""
|
33
|
+
if not (match := cls.COLUMN_START.match(text.strip())):
|
34
|
+
return None
|
168
35
|
|
169
|
-
|
170
|
-
|
171
|
-
columns_block["column_list"]["children"] = columns_children
|
36
|
+
ratio_str = match.group(1)
|
37
|
+
width_ratio = None
|
172
38
|
|
173
|
-
|
174
|
-
|
175
|
-
|
39
|
+
if ratio_str:
|
40
|
+
try:
|
41
|
+
width_ratio = float(ratio_str)
|
42
|
+
# Validate ratio is between 0 and 1
|
43
|
+
if not (0 < width_ratio <= 1.0):
|
44
|
+
width_ratio = None # Invalid ratio, use default
|
45
|
+
except ValueError:
|
46
|
+
width_ratio = None # Invalid format, use default
|
176
47
|
|
177
|
-
|
48
|
+
column_content = ColumnBlock(width_ratio=width_ratio)
|
49
|
+
return CreateColumnBlock(column=column_content)
|
178
50
|
|
179
51
|
@classmethod
|
180
|
-
def
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
columns_children: list[dict[str, any]],
|
185
|
-
converter_callback: Callable,
|
186
|
-
) -> int:
|
187
|
-
"""
|
188
|
-
Collect all columns within a column block structure.
|
189
|
-
|
190
|
-
Args:
|
191
|
-
lines: All lines of the text
|
192
|
-
start_index: Index to start collecting from
|
193
|
-
columns_children: List to append collected columns to
|
194
|
-
converter_callback: Callback function to convert column content
|
195
|
-
|
196
|
-
Returns:
|
197
|
-
Next line index after all columns have been processed
|
198
|
-
"""
|
199
|
-
i = start_index
|
200
|
-
in_column = False
|
201
|
-
column_content = []
|
202
|
-
|
203
|
-
while i < len(lines):
|
204
|
-
current_line = lines[i].strip()
|
205
|
-
|
206
|
-
if cls.COLUMNS_START.match(current_line):
|
207
|
-
break
|
208
|
-
|
209
|
-
if cls.COLUMN_START.match(current_line):
|
210
|
-
cls._finalize_column(
|
211
|
-
column_content, columns_children, in_column, converter_callback
|
212
|
-
)
|
213
|
-
column_content = []
|
214
|
-
in_column = True
|
215
|
-
i += 1
|
216
|
-
continue
|
52
|
+
async def notion_to_markdown(cls, block: Block) -> str:
|
53
|
+
"""Convert Notion column to markdown."""
|
54
|
+
if not cls.match_notion(block):
|
55
|
+
return ""
|
217
56
|
|
218
|
-
|
219
|
-
|
220
|
-
column_content, columns_children, in_column, converter_callback
|
221
|
-
)
|
222
|
-
column_content = []
|
223
|
-
in_column = False
|
224
|
-
i += 1
|
225
|
-
continue
|
226
|
-
|
227
|
-
if cls.BLOCK_END.match(current_line) and not in_column:
|
228
|
-
i += 1
|
229
|
-
break
|
230
|
-
|
231
|
-
if in_column:
|
232
|
-
column_content.append(lines[i])
|
233
|
-
|
234
|
-
i += 1
|
235
|
-
|
236
|
-
cls._finalize_column(
|
237
|
-
column_content, columns_children, in_column, converter_callback
|
238
|
-
)
|
239
|
-
|
240
|
-
return i
|
241
|
-
|
242
|
-
@staticmethod
|
243
|
-
def _finalize_column(
|
244
|
-
column_content: list[str],
|
245
|
-
columns_children: list[dict[str, any]],
|
246
|
-
in_column: bool,
|
247
|
-
converter_callback: Callable,
|
248
|
-
) -> None:
|
249
|
-
"""
|
250
|
-
Finalize a column by processing its content and adding it to the columns_children list.
|
251
|
-
|
252
|
-
Args:
|
253
|
-
column_content: Content lines of the column
|
254
|
-
columns_children: List to append the column block to
|
255
|
-
in_column: Whether we're currently in a column (if False, does nothing)
|
256
|
-
converter_callback: Callback function to convert column content
|
257
|
-
"""
|
258
|
-
if not (in_column and column_content):
|
259
|
-
return
|
260
|
-
|
261
|
-
processed_content = ColumnElement._preprocess_column_content(column_content)
|
262
|
-
|
263
|
-
column_blocks = converter_callback("\n".join(processed_content))
|
264
|
-
|
265
|
-
# Create column block
|
266
|
-
column_block = {"type": "column", "column": {"children": column_blocks}}
|
267
|
-
columns_children.append(column_block)
|
268
|
-
|
269
|
-
@classmethod
|
270
|
-
def is_multiline(cls) -> bool:
|
271
|
-
"""Column blocks span multiple lines."""
|
272
|
-
return True
|
57
|
+
if not block.column.width_ratio:
|
58
|
+
return "::: column"
|
273
59
|
|
274
|
-
|
275
|
-
def _preprocess_column_content(lines: list[str]) -> list[str]:
|
276
|
-
"""Remove all spacer markers from column content."""
|
277
|
-
return [line for line in lines if line.strip() != "---spacer---"]
|
60
|
+
return f"::: column {block.column.width_ratio}"
|
278
61
|
|
279
62
|
@classmethod
|
280
|
-
def
|
281
|
-
"""
|
282
|
-
|
283
|
-
"""
|
284
|
-
return (
|
285
|
-
ElementPromptBuilder()
|
286
|
-
.with_description(
|
287
|
-
"Creates a multi-column layout that displays content side by side."
|
288
|
-
)
|
289
|
-
.with_usage_guidelines(
|
290
|
-
"Use columns sparingly, only for direct comparisons or when parallel presentation significantly improves readability. "
|
291
|
-
"Best for pros/cons lists, feature comparisons, or pairing images with descriptions."
|
292
|
-
)
|
293
|
-
.with_avoidance_guidelines(
|
294
|
-
"Avoid overusing as it can complicate document structure. Do not use for simple content that works better in linear format."
|
295
|
-
)
|
296
|
-
.with_syntax(
|
297
|
-
"::: columns\n"
|
298
|
-
"::: column\n"
|
299
|
-
"Content for first column\n"
|
300
|
-
":::\n"
|
301
|
-
"::: column\n"
|
302
|
-
"Content for second column\n"
|
303
|
-
":::\n"
|
304
|
-
":::"
|
305
|
-
)
|
306
|
-
.with_examples(
|
307
|
-
[
|
308
|
-
"::: columns\n"
|
309
|
-
"::: column\n"
|
310
|
-
"## Features\n"
|
311
|
-
"- Fast response time\n"
|
312
|
-
"- Intuitive interface\n"
|
313
|
-
"- Regular updates\n"
|
314
|
-
":::\n"
|
315
|
-
"::: column\n"
|
316
|
-
"## Benefits\n"
|
317
|
-
"- Increased productivity\n"
|
318
|
-
"- Better collaboration\n"
|
319
|
-
"- Simplified workflows\n"
|
320
|
-
":::\n"
|
321
|
-
":::",
|
322
|
-
"::: columns\n"
|
323
|
-
"::: column\n"
|
324
|
-
"\n"
|
325
|
-
":::\n"
|
326
|
-
"::: column\n"
|
327
|
-
"This text appears next to the image, creating a media-with-caption style layout.\n"
|
328
|
-
":::\n"
|
329
|
-
":::",
|
330
|
-
]
|
331
|
-
)
|
332
|
-
.build()
|
333
|
-
)
|
63
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
64
|
+
"""Column elements are documented via ColumnListElement - return None to avoid duplication."""
|
65
|
+
return None
|
@@ -0,0 +1,52 @@
|
|
1
|
+
from typing import Optional
|
2
|
+
import re
|
3
|
+
|
4
|
+
from notionary.blocks.base_block_element import BaseBlockElement
|
5
|
+
from notionary.blocks.column.column_models import ColumnListBlock, CreateColumnListBlock
|
6
|
+
from notionary.blocks.syntax_prompt_builder import BlockElementMarkdownInformation
|
7
|
+
from notionary.blocks.models import Block, BlockCreateResult
|
8
|
+
from notionary.blocks.types import BlockType
|
9
|
+
|
10
|
+
|
11
|
+
class ColumnListElement(BaseBlockElement):
|
12
|
+
"""
|
13
|
+
Handles the `::: columns` container.
|
14
|
+
Individual columns are handled by ColumnElement.
|
15
|
+
"""
|
16
|
+
|
17
|
+
COLUMNS_START = re.compile(r"^:::\s*columns\s*$")
|
18
|
+
|
19
|
+
@classmethod
|
20
|
+
def match_markdown(cls, text: str) -> bool:
|
21
|
+
"""Check if text starts a columns container."""
|
22
|
+
return bool(cls.COLUMNS_START.match(text.strip()))
|
23
|
+
|
24
|
+
@classmethod
|
25
|
+
def match_notion(cls, block: Block) -> bool:
|
26
|
+
"""Check if block is a Notion column_list."""
|
27
|
+
return block.type == BlockType.COLUMN_LIST and block.column_list
|
28
|
+
|
29
|
+
@classmethod
|
30
|
+
async def markdown_to_notion(cls, text: str) -> BlockCreateResult:
|
31
|
+
"""Convert `::: columns` to Notion ColumnListBlock."""
|
32
|
+
if not cls.COLUMNS_START.match(text.strip()):
|
33
|
+
return None
|
34
|
+
|
35
|
+
# Empty ColumnListBlock - children (columns) added by stack processor
|
36
|
+
column_list_content = ColumnListBlock()
|
37
|
+
return CreateColumnListBlock(column_list=column_list_content)
|
38
|
+
|
39
|
+
@classmethod
|
40
|
+
@classmethod
|
41
|
+
def get_system_prompt_information(cls) -> Optional[BlockElementMarkdownInformation]:
|
42
|
+
"""Get system prompt information for column list blocks."""
|
43
|
+
return BlockElementMarkdownInformation(
|
44
|
+
block_type=cls.__name__,
|
45
|
+
description="Column list containers organize multiple columns in side-by-side layouts",
|
46
|
+
syntax_examples=[
|
47
|
+
"::: columns\n::: column\nContent 1\n:::\n::: column\nContent 2\n:::\n:::",
|
48
|
+
"::: columns\n::: column 0.6\nMain content\n:::\n::: column 0.4\nSidebar\n:::\n:::",
|
49
|
+
"::: columns\n::: column 0.25\nLeft\n:::\n::: column 0.5\nCenter\n:::\n::: column 0.25\nRight\n:::\n:::",
|
50
|
+
],
|
51
|
+
usage_guidelines="Use to create multi-column layouts with at least 2 columns. Column width ratios must add up to 1.0 when specified. Each column can contain any block content. Ends with :::.",
|
52
|
+
)
|
@@ -0,0 +1,50 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
from notionary.blocks.column.column_markdown_node import ColumnMarkdownNode
|
6
|
+
from notionary.markdown.markdown_document_model import MarkdownBlock
|
7
|
+
from notionary.markdown.markdown_node import MarkdownNode
|
8
|
+
|
9
|
+
|
10
|
+
class ColumnListMarkdownBlockParams(BaseModel):
|
11
|
+
columns: list[list[MarkdownBlock]]
|
12
|
+
model_config = {"arbitrary_types_allowed": True}
|
13
|
+
|
14
|
+
|
15
|
+
class ColumnListMarkdownNode(MarkdownNode):
|
16
|
+
"""
|
17
|
+
Programmatic interface for creating a Markdown column list container.
|
18
|
+
This represents the `::: columns` container that holds multiple columns.
|
19
|
+
|
20
|
+
Example:
|
21
|
+
::: columns
|
22
|
+
::: column
|
23
|
+
Left content
|
24
|
+
with nested lines
|
25
|
+
:::
|
26
|
+
|
27
|
+
::: column 0.3
|
28
|
+
Right content (30% width)
|
29
|
+
with nested lines
|
30
|
+
:::
|
31
|
+
:::
|
32
|
+
"""
|
33
|
+
|
34
|
+
def __init__(self, columns: list[ColumnMarkdownNode]):
|
35
|
+
self.columns = columns
|
36
|
+
|
37
|
+
@classmethod
|
38
|
+
def from_params(
|
39
|
+
cls, params: ColumnListMarkdownBlockParams
|
40
|
+
) -> ColumnListMarkdownNode:
|
41
|
+
return cls(columns=params.columns)
|
42
|
+
|
43
|
+
def to_markdown(self) -> str:
|
44
|
+
if not self.columns:
|
45
|
+
return "::: columns\n:::"
|
46
|
+
|
47
|
+
column_parts = [column.to_markdown() for column in self.columns]
|
48
|
+
columns_content = "\n\n".join(column_parts)
|
49
|
+
|
50
|
+
return f"::: columns\n{columns_content}\n:::"
|
@@ -0,0 +1,59 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
from pydantic import BaseModel
|
6
|
+
|
7
|
+
from notionary.markdown.markdown_node import MarkdownNode
|
8
|
+
|
9
|
+
|
10
|
+
class ColumnMarkdownBlockParams(BaseModel):
|
11
|
+
children: list[MarkdownNode]
|
12
|
+
width_ratio: Optional[float] = None
|
13
|
+
model_config = {"arbitrary_types_allowed": True}
|
14
|
+
|
15
|
+
|
16
|
+
class ColumnMarkdownNode(MarkdownNode):
|
17
|
+
"""
|
18
|
+
Programmatic interface for creating a single Markdown column block
|
19
|
+
with nested content and optional width ratio.
|
20
|
+
|
21
|
+
Example:
|
22
|
+
::: column
|
23
|
+
# Column Title
|
24
|
+
|
25
|
+
Some content here
|
26
|
+
:::
|
27
|
+
|
28
|
+
::: column 0.7
|
29
|
+
# Wide Column (70%)
|
30
|
+
|
31
|
+
This column takes 70% width
|
32
|
+
:::
|
33
|
+
"""
|
34
|
+
|
35
|
+
def __init__(
|
36
|
+
self, children: list[MarkdownNode], width_ratio: Optional[float] = None
|
37
|
+
):
|
38
|
+
self.children = children
|
39
|
+
self.width_ratio = width_ratio
|
40
|
+
|
41
|
+
@classmethod
|
42
|
+
def from_params(cls, params: ColumnMarkdownBlockParams) -> ColumnMarkdownNode:
|
43
|
+
return cls(children=params.children, width_ratio=params.width_ratio)
|
44
|
+
|
45
|
+
def to_markdown(self) -> str:
|
46
|
+
# Start tag with optional width ratio
|
47
|
+
if self.width_ratio is not None:
|
48
|
+
start_tag = f"::: column {self.width_ratio}"
|
49
|
+
else:
|
50
|
+
start_tag = "::: column"
|
51
|
+
|
52
|
+
if not self.children:
|
53
|
+
return f"{start_tag}\n:::"
|
54
|
+
|
55
|
+
# Convert children to markdown
|
56
|
+
content_parts = [child.to_markdown() for child in self.children]
|
57
|
+
content_text = "\n\n".join(content_parts)
|
58
|
+
|
59
|
+
return f"{start_tag}\n{content_text}\n:::"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import Literal, Optional
|
4
|
+
|
5
|
+
from pydantic import BaseModel, Field
|
6
|
+
|
7
|
+
from notionary.blocks.models import BlockCreateRequest
|
8
|
+
|
9
|
+
|
10
|
+
class ColumnBlock(BaseModel):
|
11
|
+
width_ratio: Optional[float] = None
|
12
|
+
children: list[BlockCreateRequest] = Field(default_factory=list)
|
13
|
+
|
14
|
+
|
15
|
+
class CreateColumnBlock(BaseModel):
|
16
|
+
type: Literal["column"] = "column"
|
17
|
+
column: ColumnBlock
|
18
|
+
|
19
|
+
|
20
|
+
class ColumnListBlock(BaseModel):
|
21
|
+
children: list[CreateColumnBlock] = Field(default_factory=list)
|
22
|
+
|
23
|
+
|
24
|
+
class CreateColumnListBlock(BaseModel):
|
25
|
+
type: Literal["column_list"] = "column_list"
|
26
|
+
column_list: ColumnListBlock
|