require 'nokogiri'
module Jekyll
module TOCGenerator
TOGGLE_HTML = '
%1
%2'
TOC_CONTAINER_HTML = ''
HIDE_HTML = '[%1]'
def toc_generate(html)
# No Toc can be specified on every single page
# For example the index page has no table of contents
return html if (!!@context.environments.first["page"]["toc"] == false || @context.environments.first["page"]["toc"] == false || false)
config = @context.registers[:site].config
# Minimum number of items needed to show TOC, default 0 (0 means no minimum)
min_items_to_show_toc = config["minItemsToShowToc"] || 0
anchor_prefix = config["anchorPrefix"] || 'tocAnchor-'
# better for traditional page seo, commonlly use h1 as title
toc_top_tag = config["tocTopTag"] || 'h1'
toc_top_tag = toc_top_tag.gsub(/h/, '').to_i
toc_top_tag = 5 if toc_top_tag > 5
toc_sec_tag = toc_top_tag + 1
toc_top_tag = "h#{toc_top_tag}"
toc_sec_tag = "h#{toc_sec_tag}"
# Text labels
contents_label = config["contentsLabel"] || 'Contents'
hide_label = config["hideLabel"] || 'hide'
# show_label = config["showLabel"] || 'show' # unused
show_toggle_button = config["showToggleButton"]
toc_html = ''
toc_level = 1
toc_section = 1
item_number = 1
level_html = ''
doc = Nokogiri::HTML.fragment(html)
return html unless doc.css('article').length > 0
# Find H1 tag and all its H2 siblings until next H1
doc.css(toc_top_tag).each do |tag|
# TODO This XPATH expression can greatly improved
ct = tag.xpath("count(following-sibling::#{toc_top_tag})")
# ct = tag.xpath("count(./#{toc_top_tag})")
sects = tag.xpath("following-sibling::#{toc_sec_tag}[count(following-sibling::#{toc_top_tag})=#{ct}]")
# sects = tag.xpath("following-sibling::#{toc_sec_tag}")
level_html = ''
inner_section = 0
sects.each do |sect|
inner_section += 1
anchor_id = [
anchor_prefix, toc_level, '-', toc_section, '-',
inner_section
].map(&:to_s).join ''
sect['id'] = "#{anchor_id}"
level_html += create_level_html(anchor_id,
toc_level + 1,
toc_section + inner_section,
item_number.to_s + '.' + inner_section.to_s,
sect.text,
'')
end
level_html = '' if level_html.length > 0
anchor_id = anchor_prefix + toc_level.to_s + '-' + toc_section.to_s
tag['id'] = "#{anchor_id}"
toc_html += create_level_html(anchor_id,
toc_level,
toc_section,
item_number,
tag.text,
level_html)
toc_section += 1 + inner_section
item_number += 1
end
# for convenience item_number starts from 1
# so we decrement it to obtain the index count
toc_index_count = item_number - 1
return html unless toc_html.length > 0
hide_html = ''
hide_html = HIDE_HTML.gsub('%1', hide_label) if (show_toggle_button)
if min_items_to_show_toc <= toc_index_count
replaced_toggle_html = TOGGLE_HTML
.gsub('%1', contents_label)
.gsub('%2', hide_html)
toc_table = TOC_CONTAINER_HTML
.gsub('%1', replaced_toggle_html)
.gsub('%2', toc_html)
insert_element = config["tocInsertElement"] || 'article' # default 'body'
insert_position = config["tocInsertPosition"] || 'before' # before , after
insert_children = config["tocInsertChildren"] || 'false' # before , after
if insert_element != ''
target = doc.css(insert_element)
else
target = doc
end
if insert_position == 'before'
if insert_children == true
target.children.before(toc_table)
else
target.before(toc_table)
end
elsif insert_position == 'after'
if insert_children == true
target.children.after(toc_table)
else
target.after(toc_table)
end
end
end
doc.to_html
end
private
def create_level_html(anchor_id, toc_level, toc_section, tocNumber, tocText, tocInner)
link = '%2 %3%4'
.gsub('%1', anchor_id.to_s)
.gsub('%2', tocNumber.to_s)
.gsub('%3', tocText)
.gsub('%4', tocInner ? tocInner : '')
'%3'
.gsub('%1', toc_level.to_s)
.gsub('%2', toc_section.to_s)
.gsub('%3', link)
end
end
end
Liquid::Template.register_filter(Jekyll::TOCGenerator)