Skip to content

Instantly share code, notes, and snippets.

@PeteMichaud
Created July 20, 2016 21:35
Show Gist options
  • Save PeteMichaud/dca1f5831c98d8fc18821cb4164d2b67 to your computer and use it in GitHub Desktop.
Save PeteMichaud/dca1f5831c98d8fc18821cb4164d2b67 to your computer and use it in GitHub Desktop.
A Gollum Wiki macro for better Global Table of Contents
module Gollum
# monkey patching Gollum::Page is a questionable decision. Could be refactored as a container or super class
class Page
attr_accessor :children
def pretty_title
# is this the right way to get th wiki_options from here?
@pt ||= if Precious::App.settings.wiki_options[:h1_title]
find_header_node(text_data)
end || self.url_path_display
end
private
# this is one of the jankiest parts of this code.
# The problem is that I can't use .formatted_data
# because that tries to render the whole page
# including this macro, which causes a stack overflow.
# So I just use the raw text_data. But that means the
# usual way of parsing the doc with nokogiri and
# selecting the first h1 can't work.
# Instead I just hacked it to "work" in my limited
# (but common) use case of having markdown with "#"
# being the title character.
# Ideally we might parse the document with macros
# stripped out to get the title, but maybe the
#performance hit of doing that now is too much?
def find_header_node(markdown)
#only handles markdown
markdown.split("\n").
select{|line| line =~ /^#[A-Za-z0-9\s]/ }.
first.
gsub('#','').strip
end
end
class Macro
class NestedTOC < Gollum::Macro
class TOCPage
attr_accessor :url, :title, :pages
def initialize(url, title, pages)
self.url = url
self.title = title
self.pages = convert_pages(pages)
end
def to_s
format('<li><a href="%s">%s</a>%s</li>',
self.url,
self.title,
pages_to_s
)
end
private
def convert_pages(pages)
Array(pages).map do |p|
p.is_a?(TOCPage) ? p : TOCPage.new(p.url_path, p.pretty_title, p.children)
end
end
def pages_to_s
if self.pages.size > 0
format('<ul>%s</ul>', self.pages.map(&:to_s).join)
end
end
end
def render(title = 'Site Table of Contents')
if @wiki.pages.size > 0
result = '<ul>' + nested_toc_pages.map(&:to_s).join + '</ul>'
end
"<div class='toc'><div class='toc-title'>#{title}</div>#{result}</div>"
end
private
# this is probably expensive when you get thousands of pages
def pages_with_children
pages_remaining = @wiki.pages.dup
top_level_pages = pages_remaining.reject {|p| p.url_path.include?('/') }
process_level(top_level_pages, pages_remaining - top_level_pages)
end
def process_level(pages, pages_remaining)
pages.map do |tlp|
tlp.children = select_child_pages(pages_remaining, tlp.url_path)
process_level(tlp.children, pages_remaining - tlp.children)
tlp
end
end
def select_child_pages(pages, parent_path)
pages.select do |p|
parent_path = "#{parent_path}/" unless parent_path.end_with?('/')
#this page's url path starts with the parent we're looking for, and...
p.url_path.start_with?(parent_path) &&
# also isn't part of a deeper nesting
!p.url_path[parent_path.length..-1].include?('/')
end
end
def nested_toc_pages
@nested ||= pages_with_children.map do |p|
TOCPage.new(p.url_path, p.pretty_title, p.children)
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment