Created
July 20, 2016 21:35
-
-
Save PeteMichaud/dca1f5831c98d8fc18821cb4164d2b67 to your computer and use it in GitHub Desktop.
A Gollum Wiki macro for better Global Table of Contents
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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