Repository: Python-Markdown/markdown
Pull request #885 (2019-11-26 16:08:23)

[TOC] Custom css class for anchlinks and permalinks
This is an update for #766. As that PR was on @ggueret's `master` branch, it was easier to just create a new PR than try to push to the old one.

I added a note to the release notes and moved the relevant tests to the new testing framework.


Patch:
diff --git a/docs/change_log/release-3.2.md b/docs/change_log/release-3.2.md
index 0a69284..be0d162 100644
--- a/docs/change_log/release-3.2.md
+++ b/docs/change_log/release-3.2.md
@@ -53,6 +53,12 @@ continue to see the old behavior.
 
 The following new features have been included in the release:
 
+* Two new configuration options have been added to the [toc](../extensions/toc.md)
+  extension: `anchorlink_class` and `permalink_class` which allows class(es) to be
+  assigned to the `anchorlink` and `permalink` HTML respectively. This allows using 
+  icon fonts from CSS for the links. Therefore, an empty string passed to `permalink`
+  now generates an empty `permalink`. Previously no `permalink` would have been
+  generated. (#776)
 * Document thread safety (#812).
 * Markdown parsing in HTML has been exposed via a separate extension called
   [`md_in_html`](../extensions/md_in_html.md).
diff --git a/docs/extensions/toc.md b/docs/extensions/toc.md
index 0bd17c9..bd37c9e 100644
--- a/docs/extensions/toc.md
+++ b/docs/extensions/toc.md
@@ -154,6 +154,9 @@ The following options are provided to configure the output:
 * **`anchorlink`**:
     Set to `True` to cause all headers to link to themselves. Default is `False`.
 
+* **`anchorlink_class`**:
+    CSS class(es) used for the link. Defaults to `toclink`.
+
 * **`permalink`**:
     Set to `True` or a string to generate permanent links at the end of each header.
     Useful with Sphinx style sheets.
@@ -162,6 +165,9 @@ The following options are provided to configure the output:
     the link text. When set to a string, the provided string is used as the link
     text.
 
+* **`permalink_class`**:
+    CSS class(es) used for the link. Defaults to `headerlink`.
+
 * **`baselevel`**:
     Base level for headers. Defaults to `1`.
 
diff --git a/markdown/extensions/toc.py b/markdown/extensions/toc.py
index 7629ee9..909de61 100644
--- a/markdown/extensions/toc.py
+++ b/markdown/extensions/toc.py
@@ -136,9 +136,11 @@ class TocTreeprocessor(Treeprocessor):
         self.slugify = config["slugify"]
         self.sep = config["separator"]
         self.use_anchors = parseBoolValue(config["anchorlink"])
+        self.anchorlink_class = config["anchorlink_class"]
         self.use_permalinks = parseBoolValue(config["permalink"], False)
         if self.use_permalinks is None:
             self.use_permalinks = config["permalink"]
+        self.permalink_class = config["permalink_class"]
         self.header_rgx = re.compile("[Hh][123456]")
         if isinstance(config["toc_depth"], str) and '-' in config["toc_depth"]:
             self.toc_top, self.toc_bottom = [int(x) for x in config["toc_depth"].split('-')]
@@ -184,7 +186,7 @@ class TocTreeprocessor(Treeprocessor):
         anchor = etree.Element("a")
         anchor.text = c.text
         anchor.attrib["href"] = "#" + elem_id
-        anchor.attrib["class"] = "toclink"
+        anchor.attrib["class"] = self.anchorlink_class
         c.text = ""
         for elem in c:
             anchor.append(elem)
@@ -198,7 +200,7 @@ class TocTreeprocessor(Treeprocessor):
                           if self.use_permalinks is True
                           else self.use_permalinks)
         permalink.attrib["href"] = "#" + elem_id
-        permalink.attrib["class"] = "headerlink"
+        permalink.attrib["class"] = self.permalink_class
         permalink.attrib["title"] = "Permanent link"
         c.append(permalink)
 
@@ -264,7 +266,7 @@ class TocTreeprocessor(Treeprocessor):
 
                 if self.use_anchors:
                     self.add_anchor(el, el.attrib["id"])
-                if self.use_permalinks:
+                if self.use_permalinks not in [False, None]:
                     self.add_permalink(el, el.attrib["id"])
 
         toc_tokens = nest_toc_tokens(toc_tokens)
@@ -295,9 +297,15 @@ class TocExtension(Extension):
             "anchorlink": [False,
                            "True if header should be a self link - "
                            "Defaults to False"],
+            "anchorlink_class": ['toclink',
+                                 'CSS class(es) used for the link. '
+                                 'Defaults to "toclink"'],
             "permalink": [0,
                           "True or link text if a Sphinx-style permalink should "
                           "be added - Defaults to False"],
+            "permalink_class": ['headerlink',
+                                'CSS class(es) used for the link. '
+                                'Defaults to "headerlink"'],
             "baselevel": ['1', 'Base level for headers.'],
             "slugify": [slugify,
                         "Function to generate anchors based on header text - "


Test patch:
diff --git a/tests/test_syntax/extensions/test_toc.py b/tests/test_syntax/extensions/test_toc.py
index 65223aa..65fc0c2 100644
--- a/tests/test_syntax/extensions/test_toc.py
+++ b/tests/test_syntax/extensions/test_toc.py
@@ -20,6 +20,7 @@ License: BSD (see LICENSE.md for details).
 """
 
 from markdown.test_tools import TestCase
+from markdown.extensions.toc import TocExtension
 
 
 class TestTOC(TestCase):
@@ -32,3 +33,69 @@ class TestTOC(TestCase):
             '<h1 id="escaped_character">escaped_character</h1>',
             extensions=['toc']
         )
+
+    def testAnchorLinkWithCustomClass(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                '''
+                # Header 1
+
+                ## Header *2*
+                '''
+            ),
+            self.dedent(
+                '''
+                <h1 id="header-1"><a class="custom" href="#header-1">Header 1</a></h1>
+                <h2 id="header-2"><a class="custom" href="#header-2">Header <em>2</em></a></h2>
+                '''
+            ),
+            extensions=[TocExtension(anchorlink=True, anchorlink_class="custom")]
+        )
+
+    def testAnchorLinkWithCustomClasses(self):
+        self.assertMarkdownRenders(
+            self.dedent(
+                '''
+                # Header 1
+
+                ## Header *2*
+                '''
+            ),
+            self.dedent(
+                '''
+                <h1 id="header-1"><a class="custom1 custom2" href="#header-1">Header 1</a></h1>
+                <h2 id="header-2"><a class="custom1 custom2" href="#header-2">Header <em>2</em></a></h2>
+                '''
+            ),
+            extensions=[TocExtension(anchorlink=True, anchorlink_class="custom1 custom2")]
+        )
+
+    def testPermalinkWithEmptyText(self):
+        self.assertMarkdownRenders(
+            '# Header',
+            '<h1 id="header">'                                                      # noqa
+                'Header'                                                            # noqa
+                '<a class="headerlink" href="#header" title="Permanent link"></a>'  # noqa
+            '</h1>',                                                                # noqa
+            extensions=[TocExtension(permalink="")]
+        )
+
+    def testPermalinkWithCustomClass(self):
+        self.assertMarkdownRenders(
+            '# Header',
+            '<h1 id="header">'                                                        # noqa
+                'Header'                                                              # noqa
+                '<a class="custom" href="#header" title="Permanent link">&para;</a>'  # noqa
+            '</h1>',                                                                  # noqa
+            extensions=[TocExtension(permalink=True, permalink_class="custom")]
+        )
+
+    def testPermalinkWithCustomClasses(self):
+        self.assertMarkdownRenders(
+            '# Header',
+            '<h1 id="header">'                                                                 # noqa
+                'Header'                                                                       # noqa
+                '<a class="custom1 custom2" href="#header" title="Permanent link">&para;</a>'  # noqa
+            '</h1>',                                                                           # noqa
+            extensions=[TocExtension(permalink=True, permalink_class="custom1 custom2")]
+        )