diff options
author | Robert Speicher <rspeicher@gmail.com> | 2017-08-29 14:08:59 -0400 |
---|---|---|
committer | Robert Speicher <rspeicher@gmail.com> | 2017-09-06 12:48:25 -0400 |
commit | b43aefbd9d606d01c37c5e16b081950d389e3386 (patch) | |
tree | 6b1d1678887052a5b502a1e388f5d6b01b355905 /lib/banzai | |
parent | 2a055c23c27f85db4bc56f07dccca642fc264d57 (diff) | |
download | gitlab-ce-b43aefbd9d606d01c37c5e16b081950d389e3386.tar.gz |
Refactor TableOfContentsFilter's nested table of contents
Most of the logic is now self-contained within the `HeaderNode` class.
Diffstat (limited to 'lib/banzai')
-rw-r--r-- | lib/banzai/filter/table_of_contents_filter.rb | 106 |
1 files changed, 64 insertions, 42 deletions
diff --git a/lib/banzai/filter/table_of_contents_filter.rb b/lib/banzai/filter/table_of_contents_filter.rb index 8cb860c5a92..eda94bea5e0 100644 --- a/lib/banzai/filter/table_of_contents_filter.rb +++ b/lib/banzai/filter/table_of_contents_filter.rb @@ -15,7 +15,6 @@ module Banzai # `li` child elements. class TableOfContentsFilter < HTML::Pipeline::Filter PUNCTUATION_REGEXP = /[^\p{Word}\- ]/u - HeaderNode = Struct.new(:level, :href, :text, :children, :parent) def call return doc if context[:no_header_anchors] @@ -23,56 +22,34 @@ module Banzai result[:toc] = "" headers = Hash.new(0) - - # root node of header-tree - header_root = HeaderNode.new(0, nil, nil, [], nil) - current_header = header_root + header_root = current_header = HeaderNode.new doc.css('h1, h2, h3, h4, h5, h6').each do |node| - text = node.text - - id = text.downcase - id.gsub!(PUNCTUATION_REGEXP, '') # remove punctuation - id.tr!(' ', '-') # replace spaces with dash - id.squeeze!('-') # replace multiple dashes with one - - uniq = (headers[id] > 0) ? "-#{headers[id]}" : '' - headers[id] += 1 - if header_content = node.children.first - # namespace detection will be automatically handled via javascript (see issue #22781) - namespace = "user-content-" + id = node + .text + .downcase + .gsub(PUNCTUATION_REGEXP, '') # remove punctuation + .tr(' ', '-') # replace spaces with dash + .squeeze('-') # replace multiple dashes with one + + uniq = headers[id] > 0 ? "-#{headers[id]}" : '' + headers[id] += 1 href = "#{id}#{uniq}" - 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 - - header_content.add_previous_sibling(anchor_tag("#{namespace}#{href}", href)) + current_header = HeaderNode.new(node: node, href: href, previous_header: current_header) + + header_content.add_previous_sibling(anchor_tag(href)) end end - # extract header-tree if header_root.children.length > 0 - result[:toc] = %Q{<ul class="section-nav">\n} + result[:toc] = %q{<ul class="section-nav">} + header_root.children.each do |child| push_toc(child) end + result[:toc] << '</ul>' end @@ -81,20 +58,65 @@ module Banzai private - def anchor_tag(id, href) - %Q{<a id="#{id}" class="anchor" href="##{href}" aria-hidden="true"></a>} + def anchor_tag(href) + %Q{<a id="user-content-#{href}" class="anchor" href="##{href}" aria-hidden="true"></a>} end 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' + + result[:toc] << '</li>' + end + + class HeaderNode + attr_reader :node, :href, :parent, :children + + def initialize(node: nil, href: nil, previous_header: nil) + @node = node + @href = href + @children = [] + + find_parent(previous_header) + end + + def level + return 0 unless node + + @level ||= node.name[1].to_i + end + + def text + return '' unless node + + @text ||= node.text + end + + private + + def find_parent(previous_header) + return unless previous_header + + if level == previous_header.level + @parent = previous_header.parent + elsif level > previous_header.level + @parent = previous_header + else + @parent = previous_header + @parent = @parent.parent while @parent.level >= level + end + + @parent.children.push(self) + end end end end |