# frozen_string_literal: true module Banzai module Filter # 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 def call return doc if context[:no_header_anchors] result[:toc] = +"" headers = Hash.new(0) header_root = current_header = HeaderNode.new doc.css('h1, h2, h3, h4, h5, h6').each do |node| if header_content = node.children.first id = node .text .downcase .gsub(PUNCTUATION_REGEXP, '') # remove punctuation .tr(' ', '-') # replace spaces with dash .squeeze('-') # replace multiple dashes with one .gsub(/\A(\d+)\z/, 'anchor-\1') # digits-only hrefs conflict with issue refs uniq = headers[id] > 0 ? "-#{headers[id]}" : '' headers[id] += 1 href = "#{id}#{uniq}" current_header = HeaderNode.new(node: node, href: href, previous_header: current_header) header_content.add_previous_sibling(anchor_tag(href)) end end push_toc(header_root.children, root: true) doc end private def anchor_tag(href) %Q{
} end def push_toc(children, root: false) return if children.empty? klass = ' class="section-nav"' if root result[:toc] << "