feat(notion-writer): parse standalone images to image blocks
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,7 @@ def format_id_with_dashes(raw_id: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
TABLE_SEPARATOR_RE = re.compile(r'^\s*\|?(\s*:?-{3,}:?\s*\|)+\s*:?-{3,}:?\s*\|?\s*$')
|
TABLE_SEPARATOR_RE = re.compile(r'^\s*\|?(\s*:?-{3,}:?\s*\|)+\s*:?-{3,}:?\s*\|?\s*$')
|
||||||
|
IMAGE_RE = re.compile(r'^\s*!\[([^\]]*)\]\(([^)\s]+)\)\s*$')
|
||||||
|
|
||||||
|
|
||||||
def _is_table_row(line: str) -> bool:
|
def _is_table_row(line: str) -> bool:
|
||||||
@@ -210,6 +211,13 @@ def _parse_lines(lines: List[str]) -> List[Dict[str, Any]]:
|
|||||||
blocks.append(create_column_list_block(column_blocks))
|
blocks.append(create_column_list_block(column_blocks))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
image_match = IMAGE_RE.match(line)
|
||||||
|
if image_match:
|
||||||
|
blocks.append(create_image_block(
|
||||||
|
image_match.group(1), image_match.group(2)))
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
if _is_table_row(line) and i + 1 < len(lines) and TABLE_SEPARATOR_RE.match(lines[i + 1]):
|
if _is_table_row(line) and i + 1 < len(lines) and TABLE_SEPARATOR_RE.match(lines[i + 1]):
|
||||||
header_cells = _split_table_row(line)
|
header_cells = _split_table_row(line)
|
||||||
i += 2
|
i += 2
|
||||||
@@ -507,6 +515,19 @@ def create_divider_block() -> Dict[str, Any]:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def create_image_block(alt: str, target: str) -> Dict[str, Any]:
|
||||||
|
"""Image block in external shape. Local targets are converted to
|
||||||
|
file_upload shape later by ntn_files.materialize_local_media."""
|
||||||
|
block: Dict[str, Any] = {
|
||||||
|
"object": "block",
|
||||||
|
"type": "image",
|
||||||
|
"image": {"type": "external", "external": {"url": target}},
|
||||||
|
}
|
||||||
|
if alt:
|
||||||
|
block["image"]["caption"] = parse_rich_text(alt)
|
||||||
|
return block
|
||||||
|
|
||||||
|
|
||||||
def create_table_block(header_cells: List[str], body_rows: List[List[str]]) -> Dict[str, Any]:
|
def create_table_block(header_cells: List[str], body_rows: List[List[str]]) -> Dict[str, Any]:
|
||||||
"""Build a Notion `table` block with header + body rows.
|
"""Build a Notion `table` block with header + body rows.
|
||||||
|
|
||||||
|
|||||||
@@ -413,6 +413,28 @@ def test_no_literal_markers_leak():
|
|||||||
_assert("bold" in joined and "link" in joined, "visible words preserved")
|
_assert("bold" in joined and "link" in joined, "visible words preserved")
|
||||||
|
|
||||||
|
|
||||||
|
def test_image_remote_external():
|
||||||
|
blocks = markdown_to_notion_blocks("")
|
||||||
|
assert len(blocks) == 1
|
||||||
|
b = blocks[0]
|
||||||
|
assert b["type"] == "image"
|
||||||
|
assert b["image"]["type"] == "external"
|
||||||
|
assert b["image"]["external"]["url"] == "https://ex.com/c.png"
|
||||||
|
assert b["image"]["caption"][0]["text"]["content"] == "a chart"
|
||||||
|
|
||||||
|
|
||||||
|
def test_image_local_external_shape_preupload():
|
||||||
|
blocks = markdown_to_notion_blocks("")
|
||||||
|
assert len(blocks) == 1
|
||||||
|
assert blocks[0]["image"]["external"]["url"] == "./pics/x.png"
|
||||||
|
|
||||||
|
|
||||||
|
def test_image_only_when_standalone():
|
||||||
|
# An inline bang-bracket inside prose is NOT an image block.
|
||||||
|
blocks = markdown_to_notion_blocks("see  inline")
|
||||||
|
assert blocks[0]["type"] == "paragraph"
|
||||||
|
|
||||||
|
|
||||||
def run_all():
|
def run_all():
|
||||||
tests = [
|
tests = [
|
||||||
test_rich_text_plain,
|
test_rich_text_plain,
|
||||||
@@ -447,6 +469,9 @@ def run_all():
|
|||||||
test_rich_text_relative_link_becomes_plain,
|
test_rich_text_relative_link_becomes_plain,
|
||||||
test_rich_text_absolute_link_preserved,
|
test_rich_text_absolute_link_preserved,
|
||||||
test_no_literal_markers_leak,
|
test_no_literal_markers_leak,
|
||||||
|
test_image_remote_external,
|
||||||
|
test_image_local_external_shape_preupload,
|
||||||
|
test_image_only_when_standalone,
|
||||||
]
|
]
|
||||||
for t in tests:
|
for t in tests:
|
||||||
print(f"\n{t.__name__}")
|
print(f"\n{t.__name__}")
|
||||||
|
|||||||
Reference in New Issue
Block a user