1import re
2from typing import TYPE_CHECKING, Any, Dict, Iterable
3
4if TYPE_CHECKING:
5 from ..core import BaseRenderer, BlockState
6 from ..markdown import Markdown
7
8__all__ = ["task_lists"]
9
10
11TASK_LIST_ITEM = re.compile(r"^(\[[ xX]\])\s+")
12
13
14def task_lists_hook(md: "Markdown", state: "BlockState") -> Iterable[Dict[str, Any]]:
15 return _rewrite_all_list_items(state.tokens)
16
17
18def render_task_list_item(renderer: "BaseRenderer", text: str, checked: bool = False) -> str:
19 checkbox = '<input class="task-list-item-checkbox" type="checkbox" disabled'
20 if checked:
21 checkbox += " checked/>"
22 else:
23 checkbox += "/>"
24
25 if text.startswith("<p>"):
26 text = text.replace("<p>", "<p>" + checkbox, 1)
27 else:
28 text = checkbox + text
29
30 return '<li class="task-list-item">' + text + "</li>\n"
31
32
33def task_lists(md: "Markdown") -> None:
34 """A mistune plugin to support task lists. Spec defined by
35 GitHub flavored Markdown and commonly used by many parsers:
36
37 .. code-block:: text
38
39 - [ ] unchecked task
40 - [x] checked task
41
42 :param md: Markdown instance
43 """
44 md.before_render_hooks.append(task_lists_hook)
45 if md.renderer and md.renderer.NAME == "html":
46 md.renderer.register("task_list_item", render_task_list_item)
47
48
49def _rewrite_all_list_items(tokens: Iterable[Dict[str, Any]]) -> Iterable[Dict[str, Any]]:
50 for tok in tokens:
51 if tok["type"] == "list_item":
52 _rewrite_list_item(tok)
53 if "children" in tok:
54 _rewrite_all_list_items(tok["children"])
55 return tokens
56
57
58def _rewrite_list_item(tok: Dict[str, Any]) -> None:
59 children = tok["children"]
60 if children:
61 first_child = children[0]
62 text = first_child.get("text", "")
63 m = TASK_LIST_ITEM.match(text)
64 if m:
65 mark = m.group(1)
66 first_child["text"] = text[m.end() :]
67
68 tok["type"] = "task_list_item"
69 tok["attrs"] = {"checked": mark != "[ ]"}