feat(notion-writer): add GitHub-alert callout blocks

Adds support for > [!NOTE] / > [!TIP] / > [!IMPORTANT] / > [!WARNING] /
> [!CAUTION] callouts. Each alert type maps to a Notion callout block
with an emoji icon and matching colored background. Unknown alert types
(e.g. > [!BOGUS]) fall through to the existing quote handler with the
marker preserved.

Also restores the "must come before generic bullet match" comment on
the todo branch (load-bearing ordering note).

+6 tests, 22 passing total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-27 11:12:50 +09:00
parent c318df8551
commit e0f93e6add
2 changed files with 89 additions and 2 deletions

View File

@@ -122,6 +122,7 @@ def _parse_lines(lines: List[str]) -> List[Dict[str, Any]]:
code_lines.append(lines[i])
i += 1
blocks.append(create_code_block('\n'.join(code_lines), language))
# Checkbox / Todo (must come before generic bullet match)
elif line.strip().startswith('- [ ]'):
text = line.strip()[5:].strip()
blocks.append(create_todo_block(text, False))
@@ -134,9 +135,21 @@ def _parse_lines(lines: List[str]) -> List[Dict[str, Any]]:
elif re.match(r'^\d+\.\s', line.strip()):
text = re.sub(r'^\d+\.\s', '', line.strip())
blocks.append(create_numbered_list_block(text))
# Blockquote — GitHub-alert callout takes priority over generic quote
elif line.startswith('>'):
text = line[1:].strip()
blocks.append(create_quote_block(text))
first_body = line[1:].strip()
alert_match = ALERT_RE.match(first_body)
if alert_match:
alert_type = alert_match.group(1)
body_lines: List[str] = []
i += 1
while i < len(lines) and lines[i].lstrip().startswith('>'):
body_lines.append(lines[i].lstrip()[1:].lstrip())
i += 1
body_text = '\n'.join(body_lines)
blocks.append(create_callout_block(body_text, alert_type))
continue
blocks.append(create_quote_block(first_body))
elif line.strip() in ['---', '***', '___']:
blocks.append(create_divider_block())
else:
@@ -155,6 +168,15 @@ INLINE_PATTERNS = [
('italic_under', re.compile(r'(?<![a-zA-Z0-9_])_([^_\n]+)_(?![a-zA-Z0-9_])')),
]
ALERT_TYPES = {
'NOTE': ('', 'blue_background'),
'TIP': ('💡', 'green_background'),
'IMPORTANT': ('☝️', 'purple_background'),
'WARNING': ('⚠️', 'yellow_background'),
'CAUTION': ('🚨', 'red_background'),
}
ALERT_RE = re.compile(r'^\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*$')
_ABSOLUTE_URL_RE = re.compile(r'^(?:[a-zA-Z][a-zA-Z0-9+.-]*:)')
@@ -292,6 +314,19 @@ def create_quote_block(text: str) -> Dict[str, Any]:
}
def create_callout_block(text: str, alert_type: str) -> Dict[str, Any]:
"""Create a callout block with icon + color from a GitHub alert type."""
icon, color = ALERT_TYPES[alert_type]
return {
"type": "callout",
"callout": {
"rich_text": parse_rich_text(text),
"icon": {"type": "emoji", "emoji": icon},
"color": color,
},
}
def create_code_block(code: str, language: str) -> Dict[str, Any]:
return {
"object": "block",