2015-12-15 14:51:16 +00:00
|
|
|
module Banzai
|
|
|
|
module Filter
|
2015-04-22 20:40:25 +00:00
|
|
|
# HTML filter that adds an anchor child element to all Headers in a
|
|
|
|
# document, so that they can be linked to.
|
|
|
|
#
|
|
|
|
# Generates the Table of Contents with links to each header. See Results.
|
|
|
|
#
|
|
|
|
# Based on HTML::Pipeline::TableOfContentsFilter.
|
|
|
|
#
|
|
|
|
# Context options:
|
|
|
|
# :no_header_anchors - Skips all processing done by this filter.
|
|
|
|
#
|
|
|
|
# Results:
|
|
|
|
# :toc - String containing Table of Contents data as a `ul` element with
|
|
|
|
# `li` child elements.
|
|
|
|
class TableOfContentsFilter < HTML::Pipeline::Filter
|
|
|
|
PUNCTUATION_REGEXP = /[^\p{Word}\- ]/u
|
2017-08-18 07:09:10 +00:00
|
|
|
HeaderNode = Struct.new(:level, :href, :text, :children, :parent)
|
2015-04-22 20:40:25 +00:00
|
|
|
|
|
|
|
def call
|
|
|
|
return doc if context[:no_header_anchors]
|
|
|
|
|
|
|
|
result[:toc] = ""
|
|
|
|
|
|
|
|
headers = Hash.new(0)
|
|
|
|
|
2017-08-18 07:09:10 +00:00
|
|
|
# root node of header-tree
|
|
|
|
header_root = HeaderNode.new(0, nil, nil, [], nil)
|
|
|
|
current_header = header_root
|
|
|
|
|
2015-04-22 20:40:25 +00:00
|
|
|
doc.css('h1, h2, h3, h4, h5, h6').each do |node|
|
|
|
|
text = node.text
|
|
|
|
|
|
|
|
id = text.downcase
|
|
|
|
id.gsub!(PUNCTUATION_REGEXP, '') # remove punctuation
|
2015-12-15 02:53:52 +00:00
|
|
|
id.tr!(' ', '-') # replace spaces with dash
|
2015-05-06 22:55:57 +00:00
|
|
|
id.squeeze!('-') # replace multiple dashes with one
|
2015-04-22 20:40:25 +00:00
|
|
|
|
|
|
|
uniq = (headers[id] > 0) ? "-#{headers[id]}" : ''
|
|
|
|
headers[id] += 1
|
|
|
|
|
|
|
|
if header_content = node.children.first
|
2016-11-21 18:02:19 +00:00
|
|
|
# namespace detection will be automatically handled via javascript (see issue #22781)
|
2016-11-29 19:07:38 +00:00
|
|
|
namespace = "user-content-"
|
2015-04-22 20:40:25 +00:00
|
|
|
href = "#{id}#{uniq}"
|
2017-08-18 07:09:10 +00:00
|
|
|
|
|
|
|
level = node.name[1].to_i # get this header level
|
|
|
|
if level == current_header.level
|
|
|
|
# same as previous
|
|
|
|
parent = current_header.parent
|
|
|
|
elsif level > current_header.level
|
|
|
|
# larger (weaker) than previous
|
|
|
|
parent = current_header
|
|
|
|
else
|
|
|
|
# smaller (stronger) than previous
|
|
|
|
# search parent
|
|
|
|
parent = current_header
|
|
|
|
parent = parent.parent while parent.level >= level
|
|
|
|
end
|
|
|
|
|
|
|
|
# create header-node and push as child
|
|
|
|
header_node = HeaderNode.new(level, href, text, [], parent)
|
|
|
|
parent.children.push(header_node)
|
|
|
|
current_header = header_node
|
|
|
|
|
2016-11-21 18:02:19 +00:00
|
|
|
header_content.add_previous_sibling(anchor_tag("#{namespace}#{href}", href))
|
2015-04-22 20:40:25 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-08-18 07:09:10 +00:00
|
|
|
# extract header-tree
|
|
|
|
if header_root.children.length > 0
|
|
|
|
result[:toc] = %Q{<ul class="section-nav">\n}
|
|
|
|
header_root.children.each do |child|
|
|
|
|
push_toc(child)
|
|
|
|
end
|
|
|
|
result[:toc] << '</ul>'
|
|
|
|
end
|
2015-04-22 20:40:25 +00:00
|
|
|
|
|
|
|
doc
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2016-11-21 18:02:19 +00:00
|
|
|
def anchor_tag(id, href)
|
|
|
|
%Q{<a id="#{id}" class="anchor" href="##{href}" aria-hidden="true"></a>}
|
2015-04-22 20:40:25 +00:00
|
|
|
end
|
|
|
|
|
2017-08-18 07:09:10 +00:00
|
|
|
def push_toc(header_node)
|
|
|
|
result[:toc] << %Q{<li><a href="##{header_node.href}">#{header_node.text}</a>}
|
|
|
|
if header_node.children.length > 0
|
|
|
|
result[:toc] << '<ul>'
|
|
|
|
header_node.children.each do |child|
|
|
|
|
push_toc(child)
|
|
|
|
end
|
|
|
|
result[:toc] << '</ul>'
|
|
|
|
end
|
|
|
|
result[:toc] << '</li>\n'
|
2015-04-22 20:40:25 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|