|
| 1 | +{% capture headingsWorkspace %} |
| 2 | + {% comment %} |
| 3 | + Copyright (c) 2018 Vladimir "allejo" Jimenez |
| 4 | + |
| 5 | + Permission is hereby granted, free of charge, to any person |
| 6 | + obtaining a copy of this software and associated documentation |
| 7 | + files (the "Software"), to deal in the Software without |
| 8 | + restriction, including without limitation the rights to use, |
| 9 | + copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 10 | + copies of the Software, and to permit persons to whom the |
| 11 | + Software is furnished to do so, subject to the following |
| 12 | + conditions: |
| 13 | + |
| 14 | + The above copyright notice and this permission notice shall be |
| 15 | + included in all copies or substantial portions of the Software. |
| 16 | + |
| 17 | + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| 18 | + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES |
| 19 | + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| 20 | + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
| 21 | + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 22 | + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
| 23 | + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
| 24 | + OTHER DEALINGS IN THE SOFTWARE. |
| 25 | + {% endcomment %} |
| 26 | + {% comment %} |
| 27 | + Version 1.0.12 |
| 28 | + https://github.com/allejo/jekyll-anchor-headings |
| 29 | + |
| 30 | + "Be the pull request you wish to see in the world." ~Ben Balter |
| 31 | + |
| 32 | + Usage: |
| 33 | + {% include anchor_headings.html html=content anchorBody="#" %} |
| 34 | + |
| 35 | + Parameters: |
| 36 | + * html (string) - the HTML of compiled markdown generated by kramdown in Jekyll |
| 37 | + |
| 38 | + Optional Parameters: |
| 39 | + * beforeHeading (bool) : false - Set to true if the anchor should be placed _before_ the heading's content |
| 40 | + * headerAttrs (string) : '' - Any custom HTML attributes that will be added to the heading tag; you may NOT use `id`; |
| 41 | + the `%heading%` and `%html_id%` placeholders are available |
| 42 | + * anchorAttrs (string) : '' - Any custom HTML attributes that will be added to the `<a>` tag; you may NOT use `href`, `class` or `title`; |
| 43 | + the `%heading%` and `%html_id%` placeholders are available |
| 44 | + * anchorBody (string) : '' - The content that will be placed inside the anchor; the `%heading%` placeholder is available |
| 45 | + * anchorClass (string) : '' - The class(es) that will be used for each anchor. Separate multiple classes with a space |
| 46 | + * anchorTitle (string) : '' - The `title` attribute that will be used for anchors |
| 47 | + * h_min (int) : 1 - The minimum header level to build an anchor for; any header lower than this value will be ignored |
| 48 | + * h_max (int) : 6 - The maximum header level to build an anchor for; any header greater than this value will be ignored |
| 49 | + * bodyPrefix (string) : '' - Anything that should be inserted inside of the heading tag _before_ its anchor and content |
| 50 | + * bodySuffix (string) : '' - Anything that should be inserted inside of the heading tag _after_ its anchor and content |
| 51 | + * generateId (true) : false - Set to true if a header without id should generate an id to use. |
| 52 | + |
| 53 | + Output: |
| 54 | + The original HTML with the addition of anchors inside of all of the h1-h6 headings. |
| 55 | + {% endcomment %} |
| 56 | + |
| 57 | + {% assign minHeader = include.h_min | default: 1 %} |
| 58 | + {% assign maxHeader = include.h_max | default: 6 %} |
| 59 | + {% assign beforeHeading = include.beforeHeading %} |
| 60 | + {% assign headerAttrs = include.headerAttrs %} |
| 61 | + {% assign nodes = include.html | split: '<h' %} |
| 62 | + |
| 63 | + {% capture edited_headings %}{% endcapture %} |
| 64 | + |
| 65 | + {% for _node in nodes %} |
| 66 | + {% capture node %}{{ _node | strip }}{% endcapture %} |
| 67 | + |
| 68 | + {% if node == "" %} |
| 69 | + {% continue %} |
| 70 | + {% endif %} |
| 71 | + |
| 72 | + {% assign nextChar = node | replace: '"', '' | strip | slice: 0, 1 %} |
| 73 | + {% assign headerLevel = nextChar | times: 1 %} |
| 74 | + |
| 75 | + <!-- If the level is cast to 0, it means it's not a h1-h6 tag, so let's see if we need to fix it --> |
| 76 | + {% if headerLevel == 0 %} |
| 77 | + <!-- Split up the node based on closing angle brackets and get the first one. --> |
| 78 | + {% assign firstChunk = node | split: '>' | first %} |
| 79 | + |
| 80 | + <!-- If the first chunk does NOT contain a '<', that means we've broken another HTML tag that starts with 'h' --> |
| 81 | + {% unless firstChunk contains '<' %} |
| 82 | + {% capture node %}<h{{ node }}{% endcapture %} |
| 83 | + {% endunless %} |
| 84 | + |
| 85 | + {% capture edited_headings %}{{ edited_headings }}{{ node }}{% endcapture %} |
| 86 | + {% continue %} |
| 87 | + {% endif %} |
| 88 | + |
| 89 | + {% capture _closingTag %}</h{{ headerLevel }}>{% endcapture %} |
| 90 | + {% assign _workspace = node | split: _closingTag %} |
| 91 | + {% capture _hAttrToStrip %}{{ _workspace[0] | split: '>' | first }}>{% endcapture %} |
| 92 | + {% assign header = _workspace[0] | replace: _hAttrToStrip, '' %} |
| 93 | + {% assign escaped_header = header | strip_html | strip %} |
| 94 | + |
| 95 | + {% assign _classWorkspace = _workspace[0] | split: 'class="' %} |
| 96 | + {% assign _classWorkspace = _classWorkspace[1] | split: '"' %} |
| 97 | + {% assign _html_class = _classWorkspace[0] %} |
| 98 | + |
| 99 | + {% if _html_class contains "no_anchor" %} |
| 100 | + {% assign skip_anchor = true %} |
| 101 | + {% else %} |
| 102 | + {% assign skip_anchor = false %} |
| 103 | + {% endif %} |
| 104 | + |
| 105 | + {% assign _idWorkspace = _workspace[0] | split: 'id="' %} |
| 106 | + {% if _idWorkspace[1] %} |
| 107 | + {% assign _idWorkspace = _idWorkspace[1] | split: '"' %} |
| 108 | + {% assign html_id = _idWorkspace[0] %} |
| 109 | + {% elsif include.generateId %} |
| 110 | + <!-- If the header did not have an id we create one. --> |
| 111 | + {% assign html_id = escaped_header | slugify %} |
| 112 | + {% if html_id == "" %} |
| 113 | + {% assign html_id = false %} |
| 114 | + {% endif %} |
| 115 | + {% capture headerAttrs %}{{ headerAttrs }} id="%html_id%"{% endcapture %} |
| 116 | + {% endif %} |
| 117 | + |
| 118 | + <!-- Build the anchor to inject for our heading --> |
| 119 | + {% capture anchor %}{% endcapture %} |
| 120 | + |
| 121 | + {% if skip_anchor == false and html_id and headerLevel >= minHeader and headerLevel <= maxHeader %} |
| 122 | + {% if headerAttrs %} |
| 123 | + {% capture _hAttrToStrip %}{{ _hAttrToStrip | split: '>' | first }} {{ headerAttrs | replace: '%heading%', escaped_header | replace: '%html_id%', html_id }}>{% endcapture %} |
| 124 | + {% endif %} |
| 125 | + |
| 126 | + {% capture anchor %}href="#{{ html_id }}"{% endcapture %} |
| 127 | + |
| 128 | + {% if include.anchorClass %} |
| 129 | + {% capture anchor %}{{ anchor }} class="{{ include.anchorClass }}"{% endcapture %} |
| 130 | + {% endif %} |
| 131 | + |
| 132 | + {% if include.anchorTitle %} |
| 133 | + {% capture anchor %}{{ anchor }} title="{{ include.anchorTitle | replace: '%heading%', escaped_header }}"{% endcapture %} |
| 134 | + {% endif %} |
| 135 | + |
| 136 | + {% if include.anchorAttrs %} |
| 137 | + {% capture anchor %}{{ anchor }} {{ include.anchorAttrs | replace: '%heading%', escaped_header | replace: '%html_id%', html_id }}{% endcapture %} |
| 138 | + {% endif %} |
| 139 | + |
| 140 | + {% capture anchor %}<a {{ anchor }}>{{ include.anchorBody | replace: '%heading%', escaped_header | default: '' }}</a>{% endcapture %} |
| 141 | + |
| 142 | + <!-- In order to prevent adding extra space after a heading, we'll let the 'anchor' value contain it --> |
| 143 | + {% if beforeHeading %} |
| 144 | + {% capture anchor %}{{ anchor }} {% endcapture %} |
| 145 | + {% else %} |
| 146 | + {% capture anchor %} {{ anchor }}{% endcapture %} |
| 147 | + {% endif %} |
| 148 | + {% endif %} |
| 149 | + |
| 150 | + {% capture new_heading %} |
| 151 | +<h{{ _hAttrToStrip }} |
| 152 | + {{ include.bodyPrefix }} |
| 153 | + {% if beforeHeading %} |
| 154 | + {{ anchor }}{{ header }} |
| 155 | + {% else %} |
| 156 | + {{ header }}{{ anchor }} |
| 157 | + {% endif %} |
| 158 | + {{ include.bodySuffix }} |
| 159 | +</h{{ headerLevel }}> |
| 160 | + {% endcapture %} |
| 161 | + |
| 162 | + <!-- |
| 163 | + If we have content after the `</hX>` tag, then we'll want to append that here so we don't lost any content. |
| 164 | + --> |
| 165 | + {% assign chunkCount = _workspace | size %} |
| 166 | + {% if chunkCount > 1 %} |
| 167 | + {% capture new_heading %}{{ new_heading }}{{ _workspace | last }}{% endcapture %} |
| 168 | + {% endif %} |
| 169 | + |
| 170 | + {% capture edited_headings %}{{ edited_headings }}{{ new_heading }}{% endcapture %} |
| 171 | + {% endfor %} |
| 172 | +{% endcapture %}{% assign headingsWorkspace = '' %}{{ edited_headings | strip }} |
0 commit comments