|
1 | 1 | require 'nokogiri' |
2 | 2 |
|
3 | 3 | module Jekyll |
| 4 | + |
4 | 5 | module TOCGenerator |
| 6 | + |
5 | 7 | TOGGLE_HTML = '<div id="toctitle"><h2>%1</h2>%2</div>' |
6 | 8 | TOC_CONTAINER_HTML = '<div id="toc-container"><table class="toc" id="toc"><tbody><tr><td>%1<ul>%2</ul></td></tr></tbody></table></div>' |
7 | 9 | HIDE_HTML = '<span class="toctoggle">[<a id="toctogglelink" class="internal" href="#">%1</a>]</span>' |
8 | 10 |
|
9 | | - def toc_generate(html) |
10 | | - # No Toc can be specified on every single page |
11 | | - # For example the index page has no table of contents |
12 | | - no_toc = @context.environments.first["page"]["noToc"] || false; |
| 11 | + def toc_generate(html) |
| 12 | + # No Toc can be specified on every single page |
| 13 | + # For example the index page has no table of contents |
| 14 | + no_toc = @context.environments.first["page"]["noToc"] || false; |
13 | 15 |
|
14 | | - if no_toc |
15 | | - return html |
16 | | - end |
| 16 | + return html if no_toc |
17 | 17 |
|
18 | | - config = @context.registers[:site].config |
19 | | - # Minimum number of items needed to show TOC, default 0 (0 means no minimum) |
20 | | - min_items_to_show_toc = config["minItemsToShowToc"] || 0 |
| 18 | + config = @context.registers[:site].config |
21 | 19 |
|
22 | | - anchor_prefix = config["anchorPrefix"] || 'tocAnchor-' |
| 20 | + # Minimum number of items needed to show TOC, default 0 (0 means no minimum) |
| 21 | + min_items_to_show_toc = config["minItemsToShowToc"] || 0 |
23 | 22 |
|
24 | | - # better for traditional page seo, commonlly use h1 as title |
25 | | - toc_top_tag = config["tocTopTag"] || 'h1' |
26 | | - toc_top_tag = toc_top_tag.gsub(/h/, '').to_i |
27 | | - if toc_top_tag > 5 |
28 | | - toc_top_tag = 5 |
29 | | - end |
30 | | - toc_sec_tag = toc_top_tag + 1 |
31 | | - toc_top_tag = "h#{toc_top_tag}" |
32 | | - toc_sec_tag = "h#{toc_sec_tag}" |
33 | | - |
34 | | - |
35 | | - # Text labels |
36 | | - contents_label = config["contentsLabel"] || 'Contents' |
37 | | - hide_label = config["hideLabel"] || 'hide' |
38 | | - show_label = config["showLabel"] || 'show' |
39 | | - show_toggle_button = config["showToggleButton"] |
40 | | - |
41 | | - toc_html = '' |
42 | | - toc_level = 1 |
43 | | - toc_section = 1 |
44 | | - item_number = 1 |
45 | | - level_html = '' |
46 | | - |
47 | | - doc = Nokogiri::HTML(html) |
48 | | - |
49 | | - # Find H1 tag and all its H2 siblings until next H1 |
50 | | - doc.css(toc_top_tag).each do |tag| |
51 | | - # TODO This XPATH expression can greatly improved |
52 | | - ct = tag.xpath("count(following-sibling::#{toc_top_tag})") |
53 | | - sects = tag.xpath("following-sibling::#{toc_sec_tag}[count(following-sibling::#{toc_top_tag})=#{ct}]") |
54 | | - |
55 | | - level_html = ''; |
56 | | - inner_section = 0; |
57 | | - |
58 | | - sects.map.each do |sect| |
59 | | - inner_section += 1; |
60 | | - anchor_id = anchor_prefix + toc_level.to_s + '-' + toc_section.to_s + '-' + inner_section.to_s |
61 | | - sect['id'] = "#{anchor_id}" |
62 | | - |
63 | | - level_html += create_level_html(anchor_id, |
64 | | - toc_level + 1, |
65 | | - toc_section + inner_section, |
66 | | - item_number.to_s + '.' + inner_section.to_s, |
67 | | - sect.text, |
68 | | - '') |
69 | | - end |
70 | | - if level_html.length > 0 |
71 | | - level_html = '<ul>' + level_html + '</ul>'; |
72 | | - end |
73 | | - |
74 | | - anchor_id = anchor_prefix + toc_level.to_s + '-' + toc_section.to_s; |
75 | | - tag['id'] = "#{anchor_id}" |
76 | | - |
77 | | - toc_html += create_level_html(anchor_id, |
78 | | - toc_level, |
79 | | - toc_section, |
80 | | - item_number, |
81 | | - tag.text, |
82 | | - level_html); |
83 | | - |
84 | | - toc_section += 1 + inner_section; |
85 | | - item_number += 1; |
86 | | - end |
| 23 | + anchor_prefix = config["anchorPrefix"] || 'tocAnchor-' |
| 24 | + |
| 25 | + # better for traditional page seo, commonlly use h1 as title |
| 26 | + toc_top_tag = config["tocTopTag"] || 'h1' |
| 27 | + toc_top_tag = toc_top_tag.gsub(/h/, '').to_i |
| 28 | + |
| 29 | + toc_top_tag = 5 if toc_top_tag > 5 |
| 30 | + |
| 31 | + toc_sec_tag = toc_top_tag + 1 |
| 32 | + toc_top_tag = "h#{toc_top_tag}" |
| 33 | + toc_sec_tag = "h#{toc_sec_tag}" |
| 34 | + |
| 35 | + |
| 36 | + # Text labels |
| 37 | + contents_label = config["contentsLabel"] || 'Contents' |
| 38 | + hide_label = config["hideLabel"] || 'hide' |
| 39 | + # show_label = config["showLabel"] || 'show' # unused |
| 40 | + show_toggle_button = config["showToggleButton"] |
| 41 | + |
| 42 | + toc_html = '' |
| 43 | + toc_level = 1 |
| 44 | + toc_section = 1 |
| 45 | + item_number = 1 |
| 46 | + level_html = '' |
| 47 | + |
| 48 | + doc = Nokogiri::HTML(html) |
| 49 | + |
| 50 | + # Find H1 tag and all its H2 siblings until next H1 |
| 51 | + doc.css(toc_top_tag).each do |tag| |
| 52 | + # TODO This XPATH expression can greatly improved |
| 53 | + ct = tag.xpath("count(following-sibling::#{toc_top_tag})") |
| 54 | + sects = tag.xpath("following-sibling::#{toc_sec_tag}[count(following-sibling::#{toc_top_tag})=#{ct}]") |
| 55 | + |
| 56 | + level_html = ''; |
| 57 | + inner_section = 0; |
| 58 | + |
| 59 | + sects.map.each do |sect| |
| 60 | + inner_section += 1; |
| 61 | + anchor_id = [ |
| 62 | + anchor_prefix, toc_level, '-', toc_section, '-', |
| 63 | + inner_section |
| 64 | + ].map(&:to_s).join '' |
| 65 | + |
| 66 | + sect['id'] = "#{anchor_id}" |
87 | 67 |
|
88 | | - # for convenience item_number starts from 1 |
89 | | - # so we decrement it to obtain the index count |
90 | | - toc_index_count = item_number - 1 |
91 | | - |
92 | | - if toc_html.length > 0 |
93 | | - hide_html = ''; |
94 | | - if (show_toggle_button) |
95 | | - hide_html = HIDE_HTML.gsub('%1', hide_label) |
96 | | - end |
97 | | - |
98 | | - if min_items_to_show_toc <= toc_index_count |
99 | | - replaced_toggle_html = TOGGLE_HTML |
100 | | - .gsub('%1', contents_label) |
101 | | - .gsub('%2', hide_html); |
102 | | - toc_table = TOC_CONTAINER_HTML |
103 | | - .gsub('%1', replaced_toggle_html) |
104 | | - .gsub('%2', toc_html); |
105 | | - doc.css('body').children.before(toc_table) |
106 | | - end |
107 | | - doc.css('body').children.to_xhtml(indent:3, indent_text:" ") |
108 | | - else |
109 | | - return html |
| 68 | + level_html += create_level_html(anchor_id, |
| 69 | + toc_level + 1, |
| 70 | + toc_section + inner_section, |
| 71 | + item_number.to_s + '.' + inner_section.to_s, |
| 72 | + sect.text, |
| 73 | + '') |
110 | 74 | end |
111 | | - end |
112 | 75 |
|
113 | | -private |
114 | | - |
| 76 | + level_html = '<ul>' + level_html + '</ul>' if level_html.length > 0 |
| 77 | + |
| 78 | + anchor_id = anchor_prefix + toc_level.to_s + '-' + toc_section.to_s; |
| 79 | + tag['id'] = "#{anchor_id}" |
| 80 | + |
| 81 | + toc_html += create_level_html(anchor_id, |
| 82 | + toc_level, |
| 83 | + toc_section, |
| 84 | + item_number, |
| 85 | + tag.text, |
| 86 | + level_html); |
| 87 | + |
| 88 | + toc_section += 1 + inner_section; |
| 89 | + item_number += 1; |
| 90 | + end |
| 91 | + |
| 92 | + # for convenience item_number starts from 1 |
| 93 | + # so we decrement it to obtain the index count |
| 94 | + toc_index_count = item_number - 1 |
| 95 | + |
| 96 | + return html unless toc_html.length > 0 |
| 97 | + |
| 98 | + hide_html = ''; |
| 99 | + hide_html = HIDE_HTML.gsub('%1', hide_label) if (show_toggle_button) |
| 100 | + |
| 101 | + if min_items_to_show_toc <= toc_index_count |
| 102 | + replaced_toggle_html = TOGGLE_HTML |
| 103 | + .gsub('%1', contents_label) |
| 104 | + .gsub('%2', hide_html); |
| 105 | + |
| 106 | + toc_table = TOC_CONTAINER_HTML |
| 107 | + .gsub('%1', replaced_toggle_html) |
| 108 | + .gsub('%2', toc_html); |
| 109 | + |
| 110 | + doc.css('body').children.before(toc_table) |
| 111 | + end |
| 112 | + |
| 113 | + doc.to_xhtml |
| 114 | + end |
| 115 | + |
| 116 | + private |
| 117 | + |
115 | 118 | def create_level_html(anchor_id, toc_level, toc_section, tocNumber, tocText, tocInner) |
116 | | - link = '<a href="#%1"><span class="tocnumber">%2</span> <span class="toctext">%3</span></a>%4' |
117 | | - .gsub('%1', anchor_id.to_s) |
118 | | - .gsub('%2', tocNumber.to_s) |
119 | | - .gsub('%3', tocText) |
120 | | - .gsub('%4', tocInner ? tocInner : ''); |
121 | | - '<li class="toc_level-%1 toc_section-%2">%3</li>' |
122 | | - .gsub('%1', toc_level.to_s) |
123 | | - .gsub('%2', toc_section.to_s) |
124 | | - .gsub('%3', link) |
| 119 | + link = '<a href="#%1"><span class="tocnumber">%2</span> <span class="toctext">%3</span></a>%4' |
| 120 | + .gsub('%1', anchor_id.to_s) |
| 121 | + .gsub('%2', tocNumber.to_s) |
| 122 | + .gsub('%3', tocText) |
| 123 | + .gsub('%4', tocInner ? tocInner : ''); |
| 124 | + '<li class="toc_level-%1 toc_section-%2">%3</li>' |
| 125 | + .gsub('%1', toc_level.to_s) |
| 126 | + .gsub('%2', toc_section.to_s) |
| 127 | + .gsub('%3', link) |
125 | 128 | end |
| 129 | + |
126 | 130 | end |
| 131 | + |
127 | 132 | end |
128 | 133 |
|
129 | 134 | Liquid::Template.register_filter(Jekyll::TOCGenerator) |
0 commit comments