From 8e89ec504f804f805452a9e006db962f44a260b0 Mon Sep 17 00:00:00 2001 From: Arran Cudbard-Bell Date: Fri, 3 Jun 2011 23:21:59 -0700 Subject: [PATCH 01/30] Initial commit of Nokogiri based TOC generator --- lib/extensions/toc.rb | 132 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 lib/extensions/toc.rb diff --git a/lib/extensions/toc.rb b/lib/extensions/toc.rb new file mode 100644 index 00000000..ab1889f3 --- /dev/null +++ b/lib/extensions/toc.rb @@ -0,0 +1,132 @@ +module Gollum::Extensions + class Toc_gen + attr_accessor :header_tags + + NODE_CONTENT = 0 + NODE_ORDER = 1 + + def initialize (doc, settings = {}) + @doc = doc + @header_tags = ['title','h1','h2','h3','h4','h5','h6'] + end + + # Generates a table of contents for @doc using headers + # + # Returns Nokogiri::XML::Node if there were headers to parse, or nil if none were found + def generate + return unless (headings = find_headings).count > 0 + + node_count = 0 + lvl = 0 + @lvls = [].fill(0..@header_tags.count) {[]} + + headings.each do |h| + node_count += 1 + lvl_new = heading_to_lvl h + + @lvls[lvl_new].push [build_toc_node(h), node_count] + + # Pass in the highest level prior to flattening for efficiency + flatten_to_lvl(lvl, lvl_new) if lvl_new < lvl + + lvl = lvl_new + end + + flatten_to_lvl(lvl, 0) + end + + # Escape title string for use in ID attribute + # + # title - Title string + # + # Returns string + def insert_anchors + find_headings.each do |h| + rep_h = Nokogiri::XML::Node.new('a', @doc) + rep_h['name'] = anchor_id(h.content) + rep_h.add_child(h.clone) + h.replace(rep_h) + end + end + + # Escape title string for use in ID attribute + # + # title - Title string + # + # Returns string + def anchor_id (title) + CGI::escape(title) + end + + # Convert heading into list element + # + # heading - Nokogiri::XML::Node representing a single heading + # + # Returns string + def build_toc_node(heading) + xml_node = Nokogiri::XML::Node.new('li', @doc) + xml_node.add_child("" + heading.content + "") + xml_node + end + + # Search the current @doc for headings + # + # Returns Nokogiri::XML::NodeSet + def find_headings + @doc.css(@header_tags.join ' ,') + end + + # Convert specified heading to integer level + # + # heading - Nokogiri::XML::Node representing a single heading + # + # Returns int + def heading_to_lvl(heading) + heading.name.gsub('h','').to_i + end + + # Convert Nokogiri::XML:Nodes higher in the level array than the specified low_idx + # into a node tree, and either append or prepend the tree to the level specified + # by low_idx. + # + # low_idx - The level in the array to flatten to + # high - The highest level to flatten from + # + # Returns Nokogiri::XML::Nodes if low_idx is 0, else nil + def flatten_to_lvl(high_idx, low_idx = 0) + lvl = high_idx + 1 + lvl_up = nil + + @lvls[low_idx..high_idx].reverse_each do |lvl_children| + lvl -= 1 + next if lvl_children.count() == 0 || !lvl_up && (lvl_up = lvl) + + flatten_lvl(lvl_up,lvl) + lvl_up = lvl + end + + # Level 0 flattens all levels, and returns an XML node containing the TOC data + flatten_lvl(lvl_up, 0).first[NODE_CONTENT] if lvl == 0 && lvl_up + end + + # Insert all Nokogiri::XML::Nodes at lvl_src into a new ul XML::Node as children. + # Then either append or prepend the ul node, depending on whether the first child + # at the src_level appeared before or after the node in the dst_level in @doc + # + # lvl_src - The level in the array to src li XML::Nodes from + # lvl_dst - The level in the array to insert resulting ul XML::Node + # + # Returns array of Nokogiri::XML::Nodes @ lvl_dst + def flatten_lvl(lvl_src, lvl_dst) + lvl_sib = Nokogiri::XML::Node.new('ul', @doc) + src_pos = @lvls[lvl_src].first[NODE_ORDER] + @lvls[lvl_src].each {|lvl_child| lvl_sib.add_child(lvl_child[NODE_CONTENT])} + @lvls[lvl_src] = [] + + lvl_dst > 0 && src_pos < @lvls[lvl_dst].last[NODE_ORDER] ? + @lvls[lvl_dst].insert(-2,[lvl_sib, src_pos]) : + @lvls[lvl_dst].push([lvl_sib, src_pos]) + @lvls[lvl_dst] + end + end +end \ No newline at end of file From 38c943d56479732de3dccdc97235b856673db7c9 Mon Sep 17 00:00:00 2001 From: Arran Cudbard-Bell Date: Fri, 3 Jun 2011 23:23:43 -0700 Subject: [PATCH 02/30] Add supporting modifications to frontend and css --- lib/gollum/frontend/app.rb | 32 +++++++++++++--- .../frontend/public/gollum/css/gollum.css | 38 +++++++++++++++++++ lib/gollum/frontend/templates/page.mustache | 22 +++++++---- lib/gollum/frontend/views/page.rb | 6 +++ 4 files changed, 85 insertions(+), 13 deletions(-) diff --git a/lib/gollum/frontend/app.rb b/lib/gollum/frontend/app.rb index 94352f49..84c6c515 100644 --- a/lib/gollum/frontend/app.rb +++ b/lib/gollum/frontend/app.rb @@ -6,6 +6,8 @@ require 'mustache/sinatra' require 'gollum/frontend/views/layout' require 'gollum/frontend/views/editable' +require 'extensions/toc' + module Precious class App < Sinatra::Base register Mustache::Sinatra @@ -108,10 +110,20 @@ module Precious end post '/preview' do - wiki = Gollum::Wiki.new(settings.gollum_path, settings.wiki_options) - @name = "Preview" - @page = wiki.preview_page(@name, params[:content], params[:format]) - @content = @page.formatted_data + wiki = Gollum::Wiki.new(settings.gollum_path, settings.wiki_options) + @name = "Preview" + @page = wiki.preview_page(@name, params[:content], params[:format]) + @content = @page.formatted_data do + |doc| + # Insert anchors for table of contents + toc = Gollum::Extensions::Toc_gen.new doc + if (toc_content = toc.generate) + toc.insert_anchors + @toc_content = toc_content.to_xhtml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XHTML) + else + @toc_content = nil + end + end @editable = false mustache :page end @@ -195,8 +207,18 @@ module Precious if page = wiki.page(name) @page = page @name = name - @content = page.formatted_data @editable = true + @content = page.formatted_data do + |doc| + # Insert anchors for table of contents + toc = Gollum::Extensions::Toc_gen.new doc + if (toc_content = toc.generate) + toc.insert_anchors + @toc_content = toc_content.to_xhtml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XHTML) + else + @toc_content = nil + end + end mustache :page elsif file = wiki.file(name) content_type file.mime_type diff --git a/lib/gollum/frontend/public/gollum/css/gollum.css b/lib/gollum/frontend/public/gollum/css/gollum.css index e8847dcb..5419d141 100755 --- a/lib/gollum/frontend/public/gollum/css/gollum.css +++ b/lib/gollum/frontend/public/gollum/css/gollum.css @@ -71,6 +71,7 @@ a:hover, a:visited { #wiki-body { display: block; float: left; + clear: left; margin-right: 3%; margin-bottom: 40px; width: 100%; @@ -80,6 +81,42 @@ a:hover, a:visited { width: 68%; } +/* @section toc */ +#wiki-toc-main { + background-color: #F7F7F7; + border: 1px solid #DDD; + font-size: 13px; + padding: 7px; + float:left; + margin-bottom: 20px; + + border-radius: 0.5em; + -moz-border-radius: 0.5em; + -webkit-border-radius: 0.5em; +} + +#wiki-toc-main > h2 { + font-size: 24px; + border-bottom: 1px solid #CCC; + color: black; + margin-top: 5px; + margin-bottom: 20px; +} + +#wiki-toc-main ul { + -webkit-margin-before: 0px; + -webkit-padding-start: 0px; + margin-left: 1em; + margin-right: 1em; + padding-left: 0; +} + +#wiki-toc-main ul li { + line-height: 1.75em; + list-style-position: inside; + list-style-type: round; +} + /* @section rightbar */ #wiki-rightbar { background-color: #f7f7f7; @@ -141,6 +178,7 @@ a:hover, a:visited { #wiki-header #header-content { margin-bottom: 1.5em; } + #wiki-footer #footer-content { margin-top: 1.5em; } diff --git a/lib/gollum/frontend/templates/page.mustache b/lib/gollum/frontend/templates/page.mustache index d60ae5d6..f43969db 100644 --- a/lib/gollum/frontend/templates/page.mustache +++ b/lib/gollum/frontend/templates/page.mustache @@ -18,7 +18,20 @@
-