import re from typing import TYPE_CHECKING, Match if TYPE_CHECKING: from ..block_parser import BlockParser from ..core import BaseRenderer, BlockState, InlineState from ..inline_parser import InlineParser from ..markdown import Markdown __all__ = ["spoiler"] _BLOCK_SPOILER_START = re.compile(r"^ {0,3}! ?", re.M) _BLOCK_SPOILER_MATCH = re.compile(r"^( {0,3}![^\n]*\n)+$") INLINE_SPOILER_PATTERN = r">!\s*(?P.+?)\s*!<" def parse_block_spoiler(block: "BlockParser", m: Match[str], state: "BlockState") -> int: text, end_pos = block.extract_block_quote(m, state) if not text.endswith("\n"): # ensure it endswith \n to make sure # _BLOCK_SPOILER_MATCH.match works text += "\n" depth = state.depth() if not depth and _BLOCK_SPOILER_MATCH.match(text): text = _BLOCK_SPOILER_START.sub("", text) tok_type = "block_spoiler" else: tok_type = "block_quote" # scan children state child = state.child_state(text) if state.depth() >= block.max_nested_level - 1: rules = list(block.block_quote_rules) rules.remove("block_quote") else: rules = block.block_quote_rules block.parse(child, rules) token = {"type": tok_type, "children": child.tokens} if end_pos: state.prepend_token(token) return end_pos state.append_token(token) return state.cursor def parse_inline_spoiler(inline: "InlineParser", m: Match[str], state: "InlineState") -> int: text = m.group("spoiler_text") new_state = state.copy() new_state.src = text children = inline.render(new_state) state.append_token({"type": "inline_spoiler", "children": children}) return m.end() def render_block_spoiler(renderer: "BaseRenderer", text: str) -> str: return '
\n' + text + "
\n" def render_inline_spoiler(renderer: "BaseRenderer", text: str) -> str: return '' + text + "" def spoiler(md: "Markdown") -> None: """A mistune plugin to support block and inline spoiler. The syntax is inspired by stackexchange: .. code-block:: text Block level spoiler looks like block quote, but with `>!`: >! this is spoiler >! >! the content will be hidden Inline spoiler is surrounded by `>!` and `!<`, such as >! hide me !<. :param md: Markdown instance """ # reset block quote parser with block spoiler parser md.block.register("block_quote", None, parse_block_spoiler) md.inline.register("inline_spoiler", INLINE_SPOILER_PATTERN, parse_inline_spoiler) if md.renderer and md.renderer.NAME == "html": md.renderer.register("block_spoiler", render_block_spoiler) md.renderer.register("inline_spoiler", render_inline_spoiler)