diff --git a/src/mistune/plugins/__init__.py b/src/mistune/plugins/__init__.py index 6cabd92..d4c2ede 100644 --- a/src/mistune/plugins/__init__.py +++ b/src/mistune/plugins/__init__.py @@ -20,6 +20,7 @@ "ruby": "mistune.plugins.ruby.ruby", "task_lists": "mistune.plugins.task_lists.task_lists", "spoiler": "mistune.plugins.spoiler.spoiler", + "container": "mistune.plugins.container.plugin_container", } diff --git a/src/mistune/plugins/container.py b/src/mistune/plugins/container.py new file mode 100644 index 0000000..db13062 --- /dev/null +++ b/src/mistune/plugins/container.py @@ -0,0 +1,83 @@ +import re +from typing import TYPE_CHECKING, Match, Optional + +if TYPE_CHECKING: + from ..block_parser import BlockParser + from ..core import BaseRenderer, BlockState + from ..markdown import Markdown + +__all__ = ["plugin_container"] + +CONTAINER_PATTERN = r"^ {0,3}:::(?:\s+([a-zA-Z0-9_-]+))?(?:\s+(.*?))?\n((?:(?!^ {0,3}:::).*?\n)*?)^ {0,3}:::\n*" + + +def parse_container(block: "BlockParser", m: Match[str], state: "BlockState") -> Optional[int]: + pos = m.end() + + # 提取匹配的整个字符串 + matched_text = m.group(0) + + # 分割成行 + lines = matched_text.split('\n') + + # 处理第一行,提取类型和标题 + first_line = lines[0].strip() + parts = first_line.split(' ', 2) + + container_type = "" + title = "" + + if len(parts) > 1: + container_type = parts[1] + if len(parts) > 2: + title = ' '.join(parts[2:]) + + # 提取内容(中间的行) + content_lines = lines[1:-2] # 排除第一行和最后两行(:::) + content = '\n'.join(content_lines) + + # 总是添加 type 和 title 键到 attrs 中 + attrs = { + "type": container_type, + "title": title + } + + # 创建子状态来解析容器内部的内容 + child = state.child_state(content) + # 解析子状态中的内容 + block.parse(child) + + # 添加容器令牌,使用 children 字段来存储子状态的令牌 + # 这样 HTMLRenderer 就会正确处理容器内部的 Markdown 语法 + state.append_token({ + "type": "container", + "attrs": attrs, + "children": child.tokens + }) + return pos + + +def render_html_container(renderer, text: str = "", **kwargs) -> str: + type = kwargs.get("type", "") + title = kwargs.get("title", "") + + classes = ["container"] + if type: + classes.append(type) + + html = f'
\n' + if title: + html += f"

{title}

\n" + if text: + html += text + html += "
\n" + return html + + +def plugin_container(md: "Markdown") -> None: + """A mistune plugin to support custom containers.""" + # 确保容器插件的优先级高于段落插件 + md.block.register("container", CONTAINER_PATTERN, parse_container, before="paragraph") + + if md.renderer and md.renderer.NAME == "html": + md.renderer.register("container", render_html_container)