class Gollum::Filter::TOC

Inserts header anchors and creates TOC

Public Instance Methods

extract(data) click to toggle source
# File lib/gollum-lib/filter/toc.rb, line 3
def extract(data)
  data
end
process(data) click to toggle source
# File lib/gollum-lib/filter/toc.rb, line 7
def process(data)

  @doc               = Nokogiri::HTML::DocumentFragment.parse(data)
  @toc_doc           = nil
  @anchor_names      = {}
  @current_ancestors = []

  toc_str            = ''
  if @markup.sub_page && @markup.parent_page
    toc_str = @markup.parent_page.toc_data
  else
    @doc.css('h1,h2,h3,h4,h5,h6').each_with_index do |header, i|
      next if header.content.empty?
      # omit the first H1 (the page title) from the TOC if so configured
      next if (i == 0 && header.name =~ /[Hh]1/) && @markup.wiki && @markup.wiki.h1_title

      anchor_name = generate_anchor_name(header)
      
      add_anchor_to_header header, anchor_name
      add_entry_to_toc     header, anchor_name
    end
    if not @toc_doc.nil?
      toc_str = @toc_doc.to_xml(@markup.to_xml_opts)
    end

    data  = @doc.to_xml(@markup.to_xml_opts)
  end

  @markup.toc = toc_str

  data.gsub!(/\[\[_TOC_(.*?)\]\]/) do
    levels = nil
    levels_match = Regexp.last_match[1].match /\|\s*levels\s*=\s*(\d+)/
    if levels_match
      levels = levels_match[1].to_i
    end

    if levels.nil? || toc_str.empty?
      toc_str
    else
      @toc_doc ||= Nokogiri::HTML::DocumentFragment.parse(toc_str)
      toc_clone = @toc_doc.clone
      toc_clone.traverse do |e|
        if e.name == 'ul' and e.ancestors('ul').length > levels - 1
          e.remove
        end
      end
      toc_clone.to_xml(@markup.to_xml_opts)
    end
  end

  data
end

Private Instance Methods

add_anchor_to_header(header, name) click to toggle source

Creates an anchor element with the given name and adds it before the given header element.

# File lib/gollum-lib/filter/toc.rb, line 95
def add_anchor_to_header(header, name)
  anchor_element = %Q(<a class="anchor" id="#{name}" href="##{name}"><i class="fa fa-link"></i></a>)
  header.children.before anchor_element # Add anchor element before the header
end
add_entry_to_toc(header, name) click to toggle source

Adds an entry to the TOC for the given header. The generated entry is a link to the given anchor name

# File lib/gollum-lib/filter/toc.rb, line 102
def add_entry_to_toc(header, name)
  @toc_doc ||= Nokogiri::XML::DocumentFragment.parse('<div class="toc"><div class="toc-title">Table of Contents</div></div>')
  @tail ||= @toc_doc.child
  @tail_level ||= 0

  level = header.name.gsub(/[hH]/, '').to_i

  if @tail_level < level
    while @tail_level < level
      list = Nokogiri::XML::Node.new('ul', @doc)
      @tail.add_child(list)
      @tail = list.add_child(Nokogiri::XML::Node.new('li', @doc))
      @tail_level += 1
    end
  else
    while @tail_level > level
      @tail = @tail.parent.parent
      @tail_level -= 1
    end
    @tail = @tail.parent.add_child(Nokogiri::XML::Node.new('li', @doc))
  end

  # % -> %25 so anchors work on Firefox. See issue #475
  @tail.add_child(%Q{<a href="##{name}">#{header.content}</a>})
end
generate_anchor_name(header) click to toggle source

Generates the anchor name from the given header element removing all non alphanumeric characters, replacing them with single dashes.

Generates heading ancestry prefixing the headings ancestor names to the generated name.

Prefixes duplicate anchors with an index

# File lib/gollum-lib/filter/toc.rb, line 71
def generate_anchor_name(header)
  name = header.content
  level = header.name.gsub(/[hH]/, '').to_i

  # normalize the header name
  name.gsub!(/[^\d\w\u00C0-\u1FFF\u2C00-\uD7FF]/, "-")
  name.gsub!(/-+/, "-")
  name.gsub!(/^-/, "")
  name.gsub!(/-$/, "")
  name.downcase!

  @current_ancestors[level - 1] = name
  @current_ancestors = @current_ancestors.take(level)
  anchor_name = @current_ancestors.compact.join("_")

  # Ensure duplicate anchors have a unique prefix or the toc will break
  index = increment_anchor_index(anchor_name)
  anchor_name = "#{index}-#{anchor_name}" unless index.zero? # if the index is zero this name is unique

  anchor_name
end
increment_anchor_index(name) click to toggle source

Increments the number of anchors with the given name and returns the current index

# File lib/gollum-lib/filter/toc.rb, line 130
def increment_anchor_index(name)
  @anchor_names = {} if @anchor_names.nil?
  @anchor_names[name].nil? ? @anchor_names[name] = 0 : @anchor_names[name] += 1
end