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:
- Top-down: render the page and parse the result to extract the headings.
- 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 id
s (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!