Generate inpage navigation / nice API documentation

I’m trying to create nice API documentation like the Github developer documention: https://github.com/github/developer.github.com/blob/master/README.md / http://developer.github.com/v3/ (list - overview, guides, navigation etc…)

And failing at the first hurdle:

How do I generate inpage contents navigation - like how wiki pages generate a set of aref links within the page. (example: https://buddycloud.org/wiki/buddycloud_HTTP_API).

I looked at this Generating anchors for each line in markdown but can’t find something that will just pull out the H2 and H3 lines.

Is anyone else generating nice API documentation with contents and fancy formatting who could point me in the right direction?

If you’re using Markdown, and just looking for a standard Table of Contents, the RedCarpet Markdown renderer has that built in.

Add gem 'redcarpet' to your Gemfile and run bundle install.

Inside your config.rb:

set :markdown_engine, :redcarpet
set :markdown, with_toc_data: true

helpers do
  def table_of_contents(resource)
    content = File.read(resource.source_file)
    toc_renderer = Redcarpet::Render::HTML_TOC.new
    markdown = Redcarpet::Markdown.new(toc_renderer, nesting_level: 2) # nesting_level is optional
    markdown.render(content)
  end
end

Inside your layout.erb, you can place the Table of Contents anywhere:

<%= table_of_contents(current_page) %>

This will work well-enough if you’re using plain Markdown.

If you’re not using Markdown, or want to roll your own solution, there’s essentially two approaches:

  1. Top-down: render the page and parse the result to extract the headings.
  2. Bottom-up: customise the renderer to handle your extensions.

I would recommend a bottom-up approach, but I’ll cover the top-down first.

A top-down approach is fairly straightforward, but slow and perhaps more tightly coupled with your page. You can do this by rendering the resource’s markup and parsing the result with a library like Nokogiri.

Add gem 'nokogiri' to your Gemfile and run bundle install.

Inside your config.rb:

require 'nokogiri'

helpers do
  def body_for(resource)
    resource.render(layout: nil)
  end

  def doc_for(resource)
    html = body_for(resource)
    Nokogiri::HTML::DocumentFragment.parse(html)
  end

  def toc_link(heading)
    content_tag(:a, heading.text, href: "#" + heading[:id])
  end

  def toc_item(heading)
    content_tag(:li, toc_link(heading))
  end

  def heading_nodes(resource)
    doc_for(resource).css('h2')
  end

  def table_of_contents(resource)
    list = heading_nodes(resource).map do |heading|
      toc_item(heading)
    end.join
    
    content_tag(:ul, list)
  end
end

This will work, assuming your page’s rendered HTML has headings with ids (if you’re using Markdown, it’s often an option with the renderer – see RedCarpet’s with_toc_data option above):

<h2 id="a-heading">A heading</h2>
<h2 id="b-heading">B heading</h2>

As above, you could then put the following in your layout.erb (calling this in your page would cause an infinite rendering loop):

<%= table_of_contents(current_page) %>

This will work in a pinch, but if you need more fine-grained control you might find it better to extend the renderer directly. RedCarpet, for instance, supports extending Markdown in a number of ways.

Hope that helps!

Thanks for the reply. This is super useful and exactly what is needed.