jekyll-toc-plus 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7673dcb756b228f2940af9eb43c7e2e18bb42afd1aae9a508da44991d67c3428
4
+ data.tar.gz: f44af3e46bad9e0339246603ae38409ce2ef7301c1d5003bf5f86a5267fa2d0c
5
+ SHA512:
6
+ metadata.gz: 4e1552ac001c4036bf30b849a41eb0f64cb75c5de1202518cffaab3d6ce87e145ec90929047cbded341008572823128365e35c44d8a7c61a53650e30486f9f0a
7
+ data.tar.gz: ba0c7b2eb8ba036284909822ad1dada88227c0adc4c318d5ec8b782ed12bd5359759d03ea4b2be8bf16fa2aa502e1c4904b031993783ac5b263bbae7d54e92e6
data/.rubocop.yml ADDED
@@ -0,0 +1,44 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.7
3
+ NewCops: enable
4
+ Exclude:
5
+ - "*.gemspec"
6
+ - "gemfiles/*"
7
+ - "vendor/**/*"
8
+ - Rakefile
9
+ - Gemfile
10
+ require:
11
+ - rubocop-minitest
12
+ - rubocop-rake
13
+ - rubocop-performance
14
+
15
+ Metrics/MethodLength:
16
+ Enabled: false
17
+ Metrics/AbcSize:
18
+ Enabled: false
19
+ Metrics/ClassLength:
20
+ Enabled: false
21
+
22
+ Naming/FileName:
23
+ Enabled: false
24
+
25
+ Layout/LineLength:
26
+ Enabled: false
27
+ Layout/SpaceAroundMethodCallOperator:
28
+ Enabled: true
29
+
30
+ Lint/RaiseException:
31
+ Enabled: true
32
+ Lint/StructNewOverride:
33
+ Enabled: true
34
+
35
+ Style/WordArray:
36
+ Enabled: false
37
+ Style/HashEachMethods:
38
+ Enabled: true
39
+ Style/HashTransformKeys:
40
+ Enabled: true
41
+ Style/HashTransformValues:
42
+ Enabled: true
43
+ Style/ExponentialNotation:
44
+ Enabled: true
data/Appraisals ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ SUPPORTED_VERSIONS = %w[3.9 4.0 4.1 4.2 4.3].freeze
4
+
5
+ SUPPORTED_VERSIONS.each do |version|
6
+ appraise "jekyll-#{version}" do
7
+ gem 'jekyll', version
8
+ end
9
+ end
data/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ Changelog is maintained under [Github Releases](https://github.com/jannewaren/jekyll-toc-plus/releases).
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'appraisal'
6
+ gem 'minitest-reporters'
7
+ gem 'minitest'
8
+ gem 'pry'
9
+ gem 'rake'
10
+ gem 'rubocop-minitest'
11
+ gem 'rubocop-performance'
12
+ gem 'rubocop-rake'
13
+ gem 'rubocop'
14
+ gem 'simplecov', '~> 0.22.0'
data/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020-2021 Toshimaru
4
+ Copyright (c) 2025 Janne Warén
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,486 @@
1
+ # jekyll-toc-plus
2
+
3
+ ![CI](https://github.com/jannewaren/jekyll-toc-plus/workflows/CI/badge.svg)
4
+ [![Gem Version](https://badge.fury.io/rb/jekyll-toc-plus.svg)](https://badge.fury.io/rb/jekyll-toc-plus)
5
+
6
+ ## About this fork
7
+
8
+ `jekyll-toc-plus` is a fork of [`toshimaru/jekyll-toc`](https://github.com/toshimaru/jekyll-toc).
9
+ All credit for the original plugin goes to its upstream authors — this fork simply adds a
10
+ few extra options (`div_list`, `flat_list`, `toc_only_direct_text`, and per-page
11
+ `toc_config` overrides) on top of their work, and is published to RubyGems so others can
12
+ `gem install` it directly.
13
+
14
+ It is otherwise a drop-in replacement. The maintainer makes **no promise to maintain this
15
+ as a full author**, but contributions and pull requests are welcome.
16
+
17
+ ## Table of Contents
18
+
19
+ - [Installation](#installation)
20
+ - [Usage](#usage)
21
+ - [Basic Usage](#basic-usage)
22
+ - [Advanced Usage](#advanced-usage)
23
+ - [Generated HTML](#generated-html)
24
+ - [Customization](#customization)
25
+ - [Default Configuration](#default-configuration)
26
+ - [TOC levels](#toc-levels)
27
+ - [Enable TOC by default](#enable-toc-by-default)
28
+ - [Skip TOC](#skip-toc)
29
+ - [Skip TOC Sectionally](#skip-toc-sectionally)
30
+ - [TOC Only Direct Text](#toc-only-direct-text)
31
+ - [CSS Styling](#css-styling)
32
+ - [Custom CSS Class and ID](#custom-css-class-and-id)
33
+ - [Using Unordered/Ordered lists](#using-unorderedordered-lists)
34
+ - [Using divs as list elements](#using-divs-as-list-elements)
35
+ - [Using a flat list](#using-a-flat-list)
36
+ - [Alternative Tools](#alternative-tools)
37
+
38
+ ## Installation
39
+
40
+ Add jekyll-toc-plus plugin in your site's `Gemfile`, and run `bundle install`.
41
+
42
+ ```ruby
43
+ gem 'jekyll-toc-plus'
44
+ ```
45
+
46
+ Add jekyll-toc-plus to the `plugins:` section in your site's `_config.yml`.
47
+
48
+ ```yml
49
+ plugins:
50
+ - jekyll-toc-plus
51
+ ```
52
+
53
+ Set `toc: true` in posts for which you want the TOC to appear.
54
+
55
+ ```yml
56
+ ---
57
+ layout: post
58
+ title: "Welcome to Jekyll!"
59
+ toc: true
60
+ ---
61
+ ```
62
+
63
+ ## Usage
64
+
65
+ There are three Liquid filters, which can be applied to HTML content,
66
+ e.g. the Liquid variable `content` available in Jekyll's templates.
67
+
68
+ ### Basic Usage
69
+
70
+ #### `toc` filter
71
+
72
+ Add the `toc` filter to your site's `{{ content }}` (e.g. `_layouts/post.html`).
73
+
74
+ ```liquid
75
+ {{ content | toc }}
76
+ ```
77
+
78
+ This filter places the TOC directly above the content.
79
+
80
+ ### Advanced Usage
81
+
82
+ If you'd like separated TOC and content, you can use `{% toc %}` tag (or `toc_only` filter) and `inject_anchors` filter.
83
+
84
+ #### `{% toc %}` tag / `toc_only` filter
85
+
86
+ Generates the TOC itself as described [below](#generated-html).
87
+ Mostly useful in cases where the TOC should _not_ be placed immediately
88
+ above the content but at some other place of the page, i.e. an aside.
89
+
90
+ ```html
91
+ <div>
92
+ <div id="table-of-contents">
93
+ {% toc %}
94
+ </div>
95
+ <div id="markdown-content">
96
+ {{ content }}
97
+ </div>
98
+ </div>
99
+ ```
100
+
101
+ :warning: **`{% toc %}` Tag Limitation**
102
+
103
+ `{% toc %}` works only for [Jekyll Posts](https://jekyllrb.com/docs/step-by-step/08-blogging/) and [Jekyll Collections](https://jekyllrb.com/docs/collections/).
104
+ If you'd like to use `{% toc %}` except posts or collections, please use `toc_only` filter as described below.
105
+
106
+ ```html
107
+ <div>
108
+ <div id="table-of-contents">
109
+ {{ content | toc_only }}
110
+ </div>
111
+ <div id="markdown-content">
112
+ {{ content | inject_anchors }}
113
+ </div>
114
+ </div>
115
+ ```
116
+
117
+ #### `inject_anchors` filter
118
+
119
+ Injects HTML anchors into the content without actually outputting the TOC itself.
120
+ They are of the form:
121
+
122
+ ```html
123
+ <a class="anchor" href="#heading1-1" aria-hidden="true">
124
+ <span class="octicon octicon-link"></span>
125
+ </a>
126
+ ```
127
+
128
+ This is only useful when the TOC itself should be placed at some other
129
+ location with the `toc_only` filter.
130
+
131
+ ## Generated HTML
132
+
133
+ jekyll-toc generates an unordered list by default. The HTML output is as follows.
134
+
135
+ ```html
136
+ <ul id="toc" class="section-nav">
137
+ <li class="toc-entry toc-h1"><a href="#heading1">Heading.1</a>
138
+ <ul>
139
+ <li class="toc-entry toc-h2"><a href="#heading1-1">Heading.1-1</a></li>
140
+ <li class="toc-entry toc-h2"><a href="#heading1-2">Heading.1-2</a></li>
141
+ </ul>
142
+ </li>
143
+ <li class="toc-entry toc-h1"><a href="#heading2">Heading.2</a>
144
+ <ul>
145
+ <li class="toc-entry toc-h2"><a href="#heading2-1">Heading.2-1</a>
146
+ <ul>
147
+ <li class="toc-entry toc-h3"><a href="#heading2-1-1">Heading.2-1-1</a></li>
148
+ <li class="toc-entry toc-h3"><a href="#heading2-1-2">Heading.2-1-2</a></li>
149
+ </ul>
150
+ </li>
151
+ <li class="toc-entry toc-h2"><a href="#heading2-2">Heading.2-2</a></li>
152
+ </ul>
153
+ </li>
154
+ </ul>
155
+ ```
156
+
157
+ ![screenshot](https://user-images.githubusercontent.com/803398/28401295-0dcfb7ca-6d54-11e7-892b-2f2e6ca755a7.png)
158
+
159
+ ## Customization
160
+
161
+ jekyll-toc is customizable on `_config.yml`.
162
+
163
+ ### Default Configuration
164
+
165
+ ```yml
166
+ # _config.yml
167
+ toc:
168
+ min_level: 1
169
+ max_level: 6
170
+ ordered_list: false
171
+ no_toc_section_class: no_toc_section
172
+ list_id: toc
173
+ list_class: section-nav
174
+ sublist_class: ''
175
+ item_class: toc-entry
176
+ item_prefix: toc-
177
+ div_list: false
178
+ flat_list: false
179
+ toc_only_direct_text: false
180
+ ```
181
+
182
+ ### TOC levels
183
+
184
+ ```yml
185
+ # _config.yml
186
+ toc:
187
+ min_level: 2 # default: 1
188
+ max_level: 5 # default: 6
189
+ ```
190
+
191
+ The default heading range is from `<h1>` to `<h6>`.
192
+
193
+ #### Per-Page TOC Level Override
194
+
195
+ You can override the `min_level` and `max_level` settings for individual pages or posts by adding a `toc_config` key to the YAML front matter:
196
+
197
+ ```yml
198
+ ---
199
+ layout: post
200
+ title: "My Post"
201
+ toc: true
202
+ toc_config:
203
+ min_level: 2 # Override: Start from H2 for this page only
204
+ max_level: 4 # Override: Stop at H4 for this page only
205
+ ---
206
+ ```
207
+
208
+ This is useful when you want different TOC depths for different types of content.
209
+
210
+ ### Enable TOC by default
211
+
212
+ You can enable TOC by default with [Front Matter Defaults](https://jekyllrb.com/docs/configuration/front-matter-defaults/):
213
+
214
+ ```yml
215
+ # _config.yml
216
+ defaults:
217
+ - scope:
218
+ path: ""
219
+ values:
220
+ toc: true
221
+ ```
222
+
223
+ ### Skip TOC
224
+
225
+ The heading is ignored in the toc by adding `no_toc` class.
226
+
227
+ ```html
228
+ <h1>h1</h1>
229
+ <h1 class="no_toc">This heading is ignored in the TOC</h1>
230
+ <h2>h2</h2>
231
+ ```
232
+
233
+ ### Skip TOC Sectionally
234
+
235
+ The headings are ignored inside the element which has `no_toc_section` class.
236
+
237
+ ```html
238
+ <h1>h1</h1>
239
+ <div class="no_toc_section">
240
+ <h2>This heading is ignored in the TOC</h2>
241
+ <h3>This heading is ignored in the TOC</h3>
242
+ </div>
243
+ <h4>h4</h4>
244
+ ```
245
+
246
+ Which would result in only the `<h1>` & `<h4>` within the example being included in the TOC.
247
+
248
+ The class can be configured on `_config.yml`:
249
+
250
+ ```yml
251
+ # _config.yml
252
+ toc:
253
+ no_toc_section_class: exclude # default: no_toc_section
254
+ ```
255
+
256
+ Configuring multiple classes are allowed:
257
+
258
+ ```yml
259
+ # _config.yml
260
+ toc:
261
+ no_toc_section_class:
262
+ - no_toc_section
263
+ - exclude
264
+ - your_custom_skip_class_name
265
+ ```
266
+
267
+ ### toc_only_direct_text
268
+
269
+ By default, the TOC includes all text content from headings, including text within nested HTML elements. The `toc_only_direct_text` option allows you to extract only the direct text nodes from headings, excluding all child elements.
270
+
271
+ This is particularly useful when using custom HTML components or tags within headings (like badges, icons, or status indicators) that you don't want to appear in the table of contents.
272
+
273
+ ```yml
274
+ # _config.yml
275
+ toc:
276
+ toc_only_direct_text: true # default: false
277
+ ```
278
+
279
+ **Example:**
280
+
281
+ Given this heading:
282
+
283
+ ```html
284
+ <h2>formatName <tag>Required</tag></h2>
285
+ ```
286
+
287
+ - With `toc_only_direct_text: false` (default): TOC shows "formatName Required"
288
+ - With `toc_only_direct_text: true`: TOC shows "formatName"
289
+
290
+ **Per-page override:**
291
+
292
+ You can override this setting for specific pages using front matter:
293
+
294
+ ```yml
295
+ ---
296
+ layout: post
297
+ title: "My Post"
298
+ toc: true
299
+ toc_config:
300
+ toc_only_direct_text: true
301
+ ---
302
+ ```
303
+
304
+ ### CSS Styling
305
+
306
+ The toc can be modified with CSS. The sample CSS is the following.
307
+
308
+ ```css
309
+ .section-nav {
310
+ background-color: #fff;
311
+ margin: 5px 0;
312
+ padding: 10px 30px;
313
+ border: 1px solid #e8e8e8;
314
+ border-radius: 3px;
315
+ }
316
+ ```
317
+
318
+ ![screenshot](https://user-images.githubusercontent.com/803398/28401455-0ba60868-6d55-11e7-8159-0ae7591aee66.png)
319
+
320
+ Each TOC `li` entry has two CSS classes for further styling. The general `toc-entry` is applied to all `li` elements in the `ul.section-nav`.
321
+
322
+ Depending on the heading level each specific entry refers to, it has a second CSS class `toc-XX`, where `XX` is the HTML heading tag name.
323
+ For example, the TOC entry linking to a heading `<h1>...</h1>` (a single `#` in Markdown) will get the CSS class `toc-h1`.
324
+
325
+ ### Custom CSS Class and ID
326
+
327
+ You can apply custom CSS classes to the generated `<ul>` and `<li>` tags.
328
+
329
+ ```yml
330
+ # _config.yml
331
+ toc:
332
+ list_id: my-toc-id # Default: "toc"
333
+ list_class: my-list-class # Default: "section-nav"
334
+ sublist_class: my-sublist-class # Default: no class for sublists
335
+ item_class: my-item-class # Default: "toc-entry"
336
+ item_prefix: item- # Default: "toc-":
337
+ ```
338
+
339
+ ### Using Unordered/Ordered lists
340
+
341
+ By default the table of contents will be generated as an unordered list via `<ul></ul>` tags. This can be configured to use ordered lists instead `<ol></ol>`.
342
+ This can be configured in `_config.yml`:
343
+
344
+ ```yml
345
+ # _config.yml
346
+ toc:
347
+ ordered_list: true # default is false
348
+ ```
349
+
350
+ In order to change the list type, use the [list-style-type](https://developer.mozilla.org/en-US/docs/Web/CSS/list-style-type) css property.
351
+ Add a class to the `sublist_class` configuration to append it to the `ol` tags so that you can add the `list-style-type` property.
352
+
353
+ Example
354
+
355
+ ```yml
356
+ # _config.yml
357
+ toc:
358
+ ordered_list: true
359
+ list_class: my-list-class
360
+ sublist_class: my-sublist-class
361
+ ```
362
+
363
+ ```css
364
+ .my-list-class {
365
+ list-style-type: upper-alpha;
366
+ }
367
+
368
+ .my-sublist-class {
369
+ list-style-type: lower-alpha;
370
+ }
371
+ ```
372
+
373
+ This will produce:
374
+
375
+ ![screenshot](https://user-images.githubusercontent.com/7675276/85813980-a0ea5a80-b719-11ea-9458-ccf9b86a778b.png)
376
+
377
+ ### Using divs as list elements
378
+
379
+ By default, the table of contents is generated using `<ul>`, `<ol>`, and `<li>` tags. If you prefer to use `<div>` elements instead (for custom styling or accessibility reasons), you can enable this by setting the `div_list` option in your `_config.yml`:
380
+
381
+ ```yml
382
+ # _config.yml
383
+ toc:
384
+ div_list: true # default is false
385
+ ```
386
+
387
+ When `div_list` is set to `true`, the TOC will be rendered using `<div>` elements for both the list container and each entry, instead of `<ul>`, `<ol>`, and `<li>`. You can still use the `list_class`, `sublist_class`, and `item_class` options to add custom CSS classes for styling:
388
+
389
+ ```yml
390
+ # _config.yml
391
+ toc:
392
+ div_list: true
393
+ list_class: my-list-class
394
+ sublist_class: my-sublist-class
395
+ item_class: my-item-class
396
+ ```
397
+
398
+ Example CSS for styling the TOC with `<div>` elements:
399
+
400
+ ```css
401
+ .my-list-class {
402
+ /* Styles for the TOC container */
403
+ margin: 10px 0;
404
+ }
405
+
406
+ .my-item-class {
407
+ /* Styles for each TOC entry */
408
+ padding: 4px 0;
409
+ }
410
+
411
+ .my-sublist-class {
412
+ /* Styles for nested TOC containers */
413
+ margin-left: 20px;
414
+ }
415
+ ```
416
+
417
+ This will produce a TOC structure like:
418
+
419
+ ```html
420
+ <div id="toc" class="my-list-class">
421
+ <div class="my-item-class toc-h1"><a href="#heading1">Heading.1</a>
422
+ <div class="my-sublist-class">
423
+ <div class="my-item-class toc-h2"><a href="#heading1-1">Heading.1-1</a></div>
424
+ <div class="my-item-class toc-h2"><a href="#heading1-2">Heading.1-2</a></div>
425
+ </div>
426
+ </div>
427
+ <div class="my-item-class toc-h1"><a href="#heading2">Heading.2</a></div>
428
+ </div>
429
+ ```
430
+
431
+ Use this option if you want more flexibility in styling or need to avoid list semantics for accessibility or design reasons.
432
+
433
+ ### Using a flat list
434
+
435
+ By default, the table of contents is generated as a nested structure that reflects the hierarchy of headings in your content. If you prefer a flat list with no nesting (where all TOC entries appear at the same level regardless of their heading level), you can enable this by setting the `flat_list` option in your `_config.yml`:
436
+
437
+ ```yml
438
+ # _config.yml
439
+ toc:
440
+ flat_list: true # default is false
441
+ ```
442
+
443
+ When `flat_list` is set to `true`, all TOC entries will be rendered at the same level without any nesting, while still retaining the CSS classes that indicate their heading level. This is useful when you want to style headings differently based on their level but prefer a simplified, non-nested list structure.
444
+
445
+ The flat list option works with both standard lists (`<ul>`, `<ol>`) and div-based lists (when `div_list` is set to `true`).
446
+
447
+ Example with standard lists:
448
+
449
+ ```html
450
+ <ul id="toc" class="section-nav">
451
+ <li class="toc-entry toc-h1"><a href="#heading1">Heading.1</a></li>
452
+ <li class="toc-entry toc-h2"><a href="#heading1-1">Heading.1-1</a></li>
453
+ <li class="toc-entry toc-h2"><a href="#heading1-2">Heading.1-2</a></li>
454
+ <li class="toc-entry toc-h1"><a href="#heading2">Heading.2</a></li>
455
+ <li class="toc-entry toc-h2"><a href="#heading2-1">Heading.2-1</a></li>
456
+ <li class="toc-entry toc-h3"><a href="#heading2-1-1">Heading.2-1-1</a></li>
457
+ </ul>
458
+ ```
459
+
460
+ Example with div-based lists:
461
+
462
+ ```html
463
+ <div id="toc" class="section-nav">
464
+ <div class="toc-entry toc-h1"><a href="#heading1">Heading.1</a></div>
465
+ <div class="toc-entry toc-h2"><a href="#heading1-1">Heading.1-1</a></div>
466
+ <div class="toc-entry toc-h2"><a href="#heading1-2">Heading.1-2</a></div>
467
+ <div class="toc-entry toc-h1"><a href="#heading2">Heading.2</a></div>
468
+ <div class="toc-entry toc-h2"><a href="#heading2-1">Heading.2-1</a></div>
469
+ <div class="toc-entry toc-h3"><a href="#heading2-1-1">Heading.2-1-1</a></div>
470
+ </div>
471
+ ```
472
+
473
+ Use this option when you want a simplified TOC structure but still want to style entries differently based on their heading level using CSS. For example:
474
+
475
+ ```css
476
+ .toc-h1 { font-weight: bold; font-size: 1.2em; }
477
+ .toc-h2 { font-weight: normal; font-size: 1.1em; }
478
+ .toc-h3 { font-style: italic; font-size: 1em; }
479
+ ```
480
+
481
+ ## Alternative Tools
482
+
483
+ - Adding anchor to headings
484
+ - [AnchorJS](https://www.bryanbraun.com/anchorjs/)
485
+ - Generating TOC for kramdown content
486
+ - [Automatic “Table of Contents” Generation](https://kramdown.gettalong.org/converter/html.html#toc) (See also. [Create Table of Contents in kramdown](https://blog.toshima.ru/2020/05/22/kramdown-toc))
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task default: :test
4
+
5
+ require 'rake/testtask'
6
+ Rake::TestTask.new(:test) do |test|
7
+ test.libs << 'lib' << 'test'
8
+ test.pattern = 'test/**/test_*.rb'
9
+ test.verbose = true
10
+ end
@@ -0,0 +1,17 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "minitest-reporters"
7
+ gem "minitest"
8
+ gem "pry"
9
+ gem "rake"
10
+ gem "rubocop-minitest"
11
+ gem "rubocop-performance"
12
+ gem "rubocop-rake"
13
+ gem "rubocop"
14
+ gem "simplecov", "~> 0.22.0"
15
+ gem "jekyll", "3.9"
16
+
17
+ gemspec path: "../"
@@ -0,0 +1,17 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "minitest-reporters"
7
+ gem "minitest"
8
+ gem "pry"
9
+ gem "rake"
10
+ gem "rubocop-minitest"
11
+ gem "rubocop-performance"
12
+ gem "rubocop-rake"
13
+ gem "rubocop"
14
+ gem "simplecov", "~> 0.22.0"
15
+ gem "jekyll", "4.0"
16
+
17
+ gemspec path: "../"
@@ -0,0 +1,17 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "minitest-reporters"
7
+ gem "minitest"
8
+ gem "pry"
9
+ gem "rake"
10
+ gem "rubocop-minitest"
11
+ gem "rubocop-performance"
12
+ gem "rubocop-rake"
13
+ gem "rubocop"
14
+ gem "simplecov", "~> 0.22.0"
15
+ gem "jekyll", "4.1"
16
+
17
+ gemspec path: "../"
@@ -0,0 +1,17 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "minitest-reporters"
7
+ gem "minitest"
8
+ gem "pry"
9
+ gem "rake"
10
+ gem "rubocop-minitest"
11
+ gem "rubocop-performance"
12
+ gem "rubocop-rake"
13
+ gem "rubocop"
14
+ gem "simplecov", "~> 0.22.0"
15
+ gem "jekyll", "4.2"
16
+
17
+ gemspec path: "../"
@@ -0,0 +1,17 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "appraisal"
6
+ gem "minitest-reporters"
7
+ gem "minitest"
8
+ gem "pry"
9
+ gem "rake"
10
+ gem "rubocop-minitest"
11
+ gem "rubocop-performance"
12
+ gem "rubocop-rake"
13
+ gem "rubocop"
14
+ gem "simplecov", "~> 0.22.0"
15
+ gem "jekyll", "4.3"
16
+
17
+ gemspec path: "../"
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+ require 'table_of_contents/configuration'
5
+ require 'table_of_contents/parser'
6
+
7
+ module Jekyll
8
+ # toc tag for Jekyll
9
+ class TocTag < Liquid::Tag
10
+ def render(context)
11
+ return '' unless context.registers[:page]['toc']
12
+
13
+ content_html = context.registers[:page]['content']
14
+ toc_config = merge_toc_config(context)
15
+ TableOfContents::Parser.new(content_html, toc_config).build_toc
16
+ end
17
+
18
+ private
19
+
20
+ def merge_toc_config(context)
21
+ site_config = context.registers[:site].config['toc'] || {}
22
+ page_config = context.registers[:page]['toc_config'] || {}
23
+ site_config.merge(page_config)
24
+ end
25
+ end
26
+
27
+ # Jekyll Table of Contents filter plugin
28
+ module TableOfContentsFilter
29
+ # Deprecated method. Removed in v1.0.
30
+ def toc_only(html)
31
+ return '' unless toc_enabled?
32
+
33
+ TableOfContents::Parser.new(html, toc_config).build_toc
34
+ end
35
+
36
+ def inject_anchors(html)
37
+ return html unless toc_enabled?
38
+
39
+ TableOfContents::Parser.new(html, toc_config).inject_anchors_into_html
40
+ end
41
+
42
+ def toc(html)
43
+ return html unless toc_enabled?
44
+
45
+ TableOfContents::Parser.new(html, toc_config).toc
46
+ end
47
+
48
+ private
49
+
50
+ def toc_enabled?
51
+ @context.registers[:page]['toc'] == true
52
+ end
53
+
54
+ def toc_config
55
+ site_config = @context.registers[:site].config['toc'] || {}
56
+ page_config = @context.registers[:page]['toc_config'] || {}
57
+ site_config.merge(page_config)
58
+ end
59
+ end
60
+ end
61
+
62
+ Liquid::Template.register_filter(Jekyll::TableOfContentsFilter)
63
+ Liquid::Template.register_tag('toc', Jekyll::TocTag) # will be enabled at v1.0
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module TableOfContents
5
+ # jekyll-toc configuration class
6
+ class Configuration
7
+ attr_reader :toc_levels, :no_toc_class, :ordered_list, :no_toc_section_class,
8
+ :list_id, :list_class, :sublist_class, :item_class, :item_prefix, :div_list, :flat_list,
9
+ :toc_only_direct_text
10
+
11
+ DEFAULT_CONFIG = {
12
+ 'min_level' => 1,
13
+ 'max_level' => 6,
14
+ 'ordered_list' => false,
15
+ 'no_toc_section_class' => 'no_toc_section',
16
+ 'list_id' => 'toc',
17
+ 'list_class' => 'section-nav',
18
+ 'sublist_class' => '',
19
+ 'item_class' => 'toc-entry',
20
+ 'item_prefix' => 'toc-',
21
+ 'div_list' => false,
22
+ 'flat_list' => false,
23
+ 'toc_only_direct_text' => false
24
+ }.freeze
25
+
26
+ def initialize(options)
27
+ options = generate_option_hash(options)
28
+
29
+ @toc_levels = options['min_level']..options['max_level']
30
+ @ordered_list = options['ordered_list']
31
+ @no_toc_class = 'no_toc'
32
+ @no_toc_section_class = options['no_toc_section_class']
33
+ @list_id = options['list_id']
34
+ @list_class = options['list_class']
35
+ @sublist_class = options['sublist_class']
36
+ @item_class = options['item_class']
37
+ @item_prefix = options['item_prefix']
38
+ @div_list = options['div_list']
39
+ @flat_list = options['flat_list']
40
+ @toc_only_direct_text = options['toc_only_direct_text']
41
+ end
42
+
43
+ private
44
+
45
+ def generate_option_hash(options)
46
+ DEFAULT_CONFIG.merge(options)
47
+ rescue TypeError
48
+ DEFAULT_CONFIG
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module TableOfContents
5
+ # helper methods for Parser
6
+ module Helper
7
+ PUNCTUATION_REGEXP = /[^\p{Word}\- ]/u.freeze
8
+
9
+ def generate_toc_id(text)
10
+ text = text.downcase
11
+ .gsub(PUNCTUATION_REGEXP, '') # remove punctuation
12
+ .tr(' ', '-') # replace spaces with dash
13
+ CGI.escape(text)
14
+ end
15
+
16
+ def extract_text(node, only_direct_text: false)
17
+ if only_direct_text
18
+ node.children.select(&:text?).map { |child| child.text.strip }.reject(&:empty?).join(' ')
19
+ else
20
+ node.text.strip
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'table_of_contents/helper'
4
+
5
+ module Jekyll
6
+ module TableOfContents
7
+ # Parse html contents and generate table of contents
8
+ class Parser
9
+ include ::Jekyll::TableOfContents::Helper
10
+
11
+ def initialize(html, options = {})
12
+ @doc = Nokogiri::HTML::DocumentFragment.parse(html)
13
+ @configuration = Configuration.new(options)
14
+ @entries = parse_content
15
+ end
16
+
17
+ def toc
18
+ build_toc + inject_anchors_into_html
19
+ end
20
+
21
+ def build_toc
22
+ %(<#{list_tag} id="#{@configuration.list_id}" class="#{@configuration.list_class}">\n#{build_toc_list(@entries)}</#{list_tag}>)
23
+ end
24
+
25
+ def inject_anchors_into_html
26
+ @entries.each do |entry|
27
+ # NOTE: `entry[:id]` is automatically URL encoded by Nokogiri
28
+ entry[:header_content].add_previous_sibling(
29
+ %(<a class="anchor" href="##{entry[:id]}" aria-hidden="true"><span class="octicon octicon-link"></span></a>)
30
+ )
31
+ end
32
+
33
+ @doc.inner_html
34
+ end
35
+
36
+ private
37
+
38
+ # parse logic is from html-pipeline toc_filter
39
+ # https://github.com/jch/html-pipeline/blob/v1.1.0/lib/html/pipeline/toc_filter.rb
40
+ def parse_content
41
+ headers = Hash.new(0)
42
+
43
+ (@doc.css(toc_headings) - @doc.css(toc_headings_in_no_toc_section))
44
+ .reject { |n| n.classes.include?(@configuration.no_toc_class) }
45
+ .inject([]) do |entries, node|
46
+ text = extract_text(node, only_direct_text: @configuration.toc_only_direct_text)
47
+ id = node.attribute('id') || generate_toc_id(text)
48
+
49
+ suffix_num = headers[id]
50
+ headers[id] += 1
51
+
52
+ entries << {
53
+ id: suffix_num.zero? ? id : "#{id}-#{suffix_num}",
54
+ text: CGI.escapeHTML(text),
55
+ node_name: node.name,
56
+ header_content: node.children.first,
57
+ h_num: node.name.delete('h').to_i
58
+ }
59
+ end
60
+ end
61
+
62
+ # Returns the list items for entries
63
+ def build_toc_list(entries)
64
+ if @configuration.flat_list
65
+ build_flat_toc_list(entries)
66
+ else
67
+ build_nested_toc_list(entries)
68
+ end
69
+ end
70
+
71
+ # Returns the list items for entries in a flat structure
72
+ def build_flat_toc_list(entries)
73
+ toc_list = +''
74
+
75
+ entries.each do |entry|
76
+ toc_list << %(<#{list_parent_tag} class="#{@configuration.item_class} #{@configuration.item_prefix}#{entry[:node_name]}"><a href="##{entry[:id]}">#{entry[:text]}</a></#{list_parent_tag}>\n)
77
+ end
78
+
79
+ toc_list
80
+ end
81
+
82
+ # Returns the list items for entries in a nested structure
83
+ def build_nested_toc_list(entries)
84
+ i = 0
85
+ toc_list = +''
86
+ min_h_num = entries.map { |e| e[:h_num] }.min
87
+
88
+ while i < entries.count
89
+ entry = entries[i]
90
+ if entry[:h_num] == min_h_num
91
+ # If the current entry should not be indented in the list, add the entry to the list
92
+ # If the next entry should be indented in the list, generate a sublist
93
+ toc_list << %(<#{list_parent_tag} class="#{@configuration.item_class} #{@configuration.item_prefix}#{entry[:node_name]}"><a href="##{entry[:id]}">#{entry[:text]}</a>)
94
+ next_i = i + 1
95
+ if next_i < entries.count && entries[next_i][:h_num] > min_h_num
96
+ nest_entries = get_nest_entries(entries[next_i, entries.count], min_h_num)
97
+ toc_list << %(\n<#{list_tag}#{ul_attributes}>\n#{build_nested_toc_list(nest_entries)}</#{list_tag}>\n)
98
+ i += nest_entries.count
99
+ end
100
+ toc_list << %(</#{list_parent_tag}>\n)
101
+ elsif entry[:h_num] > min_h_num
102
+ nest_entries = get_nest_entries(entries[i, entries.count], min_h_num)
103
+ toc_list << build_nested_toc_list(nest_entries)
104
+ i += nest_entries.count - 1
105
+ end
106
+ i += 1
107
+ end
108
+
109
+ toc_list
110
+ end
111
+
112
+ # Returns the entries in a nested list
113
+ # The nested list starts at the first entry in entries (inclusive)
114
+ # The nested list ends at the first entry in entries with depth min_h_num or greater (exclusive)
115
+ def get_nest_entries(entries, min_h_num)
116
+ entries.inject([]) do |nest_entries, entry|
117
+ break nest_entries if entry[:h_num] == min_h_num
118
+
119
+ nest_entries << entry
120
+ end
121
+ end
122
+
123
+ def toc_headings
124
+ @configuration.toc_levels.map { |level| "h#{level}" }.join(',')
125
+ end
126
+
127
+ def toc_headings_in_no_toc_section
128
+ if @configuration.no_toc_section_class.is_a?(Array)
129
+ @configuration.no_toc_section_class.map { |cls| toc_headings_within(cls) }.join(',')
130
+ else
131
+ toc_headings_within(@configuration.no_toc_section_class)
132
+ end
133
+ end
134
+
135
+ def toc_headings_within(class_name)
136
+ @configuration.toc_levels.map { |level| ".#{class_name} h#{level}" }.join(',')
137
+ end
138
+
139
+ def ul_attributes
140
+ @ul_attributes ||= @configuration.sublist_class.empty? ? '' : %( class="#{@configuration.sublist_class}")
141
+ end
142
+
143
+ def list_parent_tag
144
+ @list_parent_tag ||= @configuration.div_list ? 'div' : 'li'
145
+ end
146
+
147
+ def list_tag
148
+ @list_tag ||= if @configuration.div_list
149
+ 'div'
150
+ else
151
+ (@configuration.ordered_list ? 'ol' : 'ul')
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module TableOfContents
5
+ VERSION = '0.20.0'
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-toc-plus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.20.0
5
+ platform: ruby
6
+ authors:
7
+ - toshimaru
8
+ - torbjoernk
9
+ - Janne Warén
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 1980-01-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: jekyll
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '3.9'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '3.9'
28
+ - !ruby/object:Gem::Dependency
29
+ name: nokogiri
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1.12'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1.12'
42
+ description: Jekyll (Ruby static website generator) plugin which generates a Table
43
+ of Contents for the page.
44
+ email: janne.waren@gmail.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".rubocop.yml"
50
+ - Appraisals
51
+ - CHANGELOG.md
52
+ - Gemfile
53
+ - LICENSE.md
54
+ - README.md
55
+ - Rakefile
56
+ - gemfiles/jekyll_3.9.gemfile
57
+ - gemfiles/jekyll_4.0.gemfile
58
+ - gemfiles/jekyll_4.1.gemfile
59
+ - gemfiles/jekyll_4.2.gemfile
60
+ - gemfiles/jekyll_4.3.gemfile
61
+ - lib/jekyll-toc-plus.rb
62
+ - lib/table_of_contents/configuration.rb
63
+ - lib/table_of_contents/helper.rb
64
+ - lib/table_of_contents/parser.rb
65
+ - lib/table_of_contents/version.rb
66
+ homepage: https://github.com/jannewaren/jekyll-toc-plus
67
+ licenses:
68
+ - MIT
69
+ metadata:
70
+ homepage_uri: https://github.com/jannewaren/jekyll-toc-plus
71
+ source_code_uri: https://github.com/jannewaren/jekyll-toc-plus
72
+ changelog_uri: https://github.com/jannewaren/jekyll-toc-plus/releases
73
+ rubygems_mfa_required: 'true'
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '2.7'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubygems_version: 4.0.10
89
+ specification_version: 4
90
+ summary: Jekyll Table of Contents plugin
91
+ test_files: []