summaryrefslogtreecommitdiffstats
path: root/mdit_py_plugins/anchors
diff options
context:
space:
mode:
Diffstat (limited to 'mdit_py_plugins/anchors')
-rw-r--r--mdit_py_plugins/anchors/__init__.py1
-rw-r--r--mdit_py_plugins/anchors/index.py129
2 files changed, 130 insertions, 0 deletions
diff --git a/mdit_py_plugins/anchors/__init__.py b/mdit_py_plugins/anchors/__init__.py
new file mode 100644
index 0000000..d9c4f05
--- /dev/null
+++ b/mdit_py_plugins/anchors/__init__.py
@@ -0,0 +1 @@
+from .index import anchors_plugin # noqa F401
diff --git a/mdit_py_plugins/anchors/index.py b/mdit_py_plugins/anchors/index.py
new file mode 100644
index 0000000..abbd48a
--- /dev/null
+++ b/mdit_py_plugins/anchors/index.py
@@ -0,0 +1,129 @@
+import re
+from typing import Callable, List, Optional, Set
+
+from markdown_it import MarkdownIt
+from markdown_it.rules_core import StateCore
+from markdown_it.token import Token
+
+
+def anchors_plugin(
+ md: MarkdownIt,
+ min_level: int = 1,
+ max_level: int = 2,
+ slug_func: Optional[Callable[[str], str]] = None,
+ permalink: bool = False,
+ permalinkSymbol: str = "¶",
+ permalinkBefore: bool = False,
+ permalinkSpace: bool = True,
+):
+ """Plugin for adding header anchors, based on
+ `markdown-it-anchor <https://github.com/valeriangalliat/markdown-it-anchor>`__
+
+ .. code-block:: md
+
+ # Title String
+
+ renders as:
+
+ .. code-block:: html
+
+ <h1 id="title-string">Title String <a class="header-anchor" href="#title-string">¶</a></h1>
+
+ :param min_level: minimum header level to apply anchors
+ :param max_level: maximum header level to apply anchors
+ :param slug_func: function to convert title text to id slug.
+ :param permalink: Add a permalink next to the title
+ :param permalinkSymbol: the symbol to show
+ :param permalinkBefore: Add the permalink before the title, otherwise after
+ :param permalinkSpace: Add a space between the permalink and the title
+
+ Note, the default slug function aims to mimic the GitHub Markdown format, see:
+
+ - https://github.com/jch/html-pipeline/blob/master/lib/html/pipeline/toc_filter.rb
+ - https://gist.github.com/asabaylus/3071099
+
+ """
+ selected_levels = list(range(min_level, max_level + 1))
+ md.core.ruler.push(
+ "anchor",
+ _make_anchors_func(
+ selected_levels,
+ slug_func or slugify,
+ permalink,
+ permalinkSymbol,
+ permalinkBefore,
+ permalinkSpace,
+ ),
+ )
+
+
+def _make_anchors_func(
+ selected_levels: List[int],
+ slug_func: Callable[[str], str],
+ permalink: bool,
+ permalinkSymbol: str,
+ permalinkBefore: bool,
+ permalinkSpace: bool,
+):
+ def _anchor_func(state: StateCore):
+ slugs: Set[str] = set()
+ for (idx, token) in enumerate(state.tokens):
+ if token.type != "heading_open":
+ continue
+ level = int(token.tag[1])
+ if level not in selected_levels:
+ continue
+ inline_token = state.tokens[idx + 1]
+ assert inline_token.children is not None
+ title = "".join(
+ child.content
+ for child in inline_token.children
+ if child.type in ["text", "code_inline"]
+ )
+ slug = unique_slug(slug_func(title), slugs)
+ token.attrSet("id", slug)
+
+ if permalink:
+ link_open = Token(
+ "link_open",
+ "a",
+ 1,
+ )
+ link_open.attrSet("class", "header-anchor")
+ link_open.attrSet("href", f"#{slug}")
+ link_tokens = [
+ link_open,
+ Token("html_block", "", 0, content=permalinkSymbol),
+ Token("link_close", "a", -1),
+ ]
+ if permalinkBefore:
+ inline_token.children = (
+ link_tokens
+ + (
+ [Token("text", "", 0, content=" ")]
+ if permalinkSpace
+ else []
+ )
+ + inline_token.children
+ )
+ else:
+ inline_token.children.extend(
+ ([Token("text", "", 0, content=" ")] if permalinkSpace else [])
+ + link_tokens
+ )
+
+ return _anchor_func
+
+
+def slugify(title: str):
+ return re.sub(r"[^\w\u4e00-\u9fff\- ]", "", title.strip().lower().replace(" ", "-"))
+
+
+def unique_slug(slug: str, slugs: set):
+ uniq = slug
+ i = 1
+ while uniq in slugs:
+ uniq = f"{slug}-{i}"
+ i += 1
+ slugs.add(uniq)
+ return uniq