Skip to content

Commit

Permalink
Static table of contents (#701)
Browse files Browse the repository at this point in the history
* Add showing/hiding submenus, fix sidebar styling, fix bug where includes wouldn't appear in ToC

* Update ToC to highlight last header if page is scrolled to very bottom, fixes #280

* Set HTML title to current h1 section text, see #133

* Fix menu not opening on mobile

* Add back increase toc item height on mobile

* Fix padding bug

* Add back in ToC sliding animation
  • Loading branch information
lord authored Feb 24, 2017
1 parent e7f5144 commit 53e2f23
Show file tree
Hide file tree
Showing 13 changed files with 202 additions and 1,697 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ gem 'middleman-autoprefixer', '~> 2.7.0'
gem "middleman-sprockets", "~> 4.1.0"
gem 'rouge', '~> 2.0.5'
gem 'redcarpet', '~> 3.4.0'
gem 'nokogiri', '~> 1.6.8'
6 changes: 5 additions & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ GEM
middleman-syntax (3.0.0)
middleman-core (>= 3.2)
rouge (~> 2.0)
mini_portile2 (2.1.0)
minitest (5.10.1)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
padrino-helpers (0.13.3.3)
i18n (~> 0.6, >= 0.6.7)
padrino-support (= 0.13.3.3)
Expand Down Expand Up @@ -115,8 +118,9 @@ DEPENDENCIES
middleman-autoprefixer (~> 2.7.0)
middleman-sprockets (~> 4.1.0)
middleman-syntax (~> 3.0.0)
nokogiri (~> 1.6.8)
redcarpet (~> 3.4.0)
rouge (~> 2.0.5)

BUNDLED WITH
1.14.3
1.14.5
4 changes: 4 additions & 0 deletions config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,7 @@
# Deploy Configuration
# If you want Middleman to listen on a different port, you can set that below
set :port, 4567

helpers do
require './lib/toc_data.rb'
end
30 changes: 30 additions & 0 deletions lib/toc_data.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require 'nokogiri'

def toc_data(page_content)
html_doc = Nokogiri::HTML::DocumentFragment.parse(page_content)

# get a flat list of headers
headers = []
html_doc.css('h1, h2, h3').each do |header|
headers.push({
id: header.attribute('id').to_s,
content: header.content,
level: header.name[1].to_i,
children: []
})
end

[3,2].each do |header_level|
header_to_nest = nil
headers = headers.reject do |header|
if header[:level] == header_level
header_to_nest[:children].push header if header_to_nest
true
else
header_to_nest = header if header[:level] == (header_level - 1)
false
end
end
end
headers
end
4 changes: 1 addition & 3 deletions source/javascripts/all.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
//= require ./lib/_energize
//= require ./app/_lang
//= require ./all_nosearch
//= require ./app/_search
//= require ./app/_toc
15 changes: 14 additions & 1 deletion source/javascripts/all_nosearch.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
//= require ./lib/_energize
//= require ./app/_lang
//= require ./app/_toc
//= require ./app/_lang

$(function() {
loadToc($('#toc'), '.toc-link', '.toc-list-h2', 10);
setupLanguages($('body').data('languages'));
$('.content').imagesLoaded( function() {
window.recacheHeights();
window.refreshToc();
});
});

window.onpopstate = function() {
activateLanguage(getLanguageFromQueryString());
};
14 changes: 6 additions & 8 deletions source/javascripts/app/_lang.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
*/
(function (global) {
;(function () {
'use strict';

var languages = [];

global.setupLanguages = setupLanguages;
global.activateLanguage = activateLanguage;
window.setupLanguages = setupLanguages;
window.activateLanguage = activateLanguage;
window.getLanguageFromQueryString = getLanguageFromQueryString;

function activateLanguage(language) {
if (!language) return;
Expand All @@ -36,7 +37,7 @@ under the License.
$(".highlight.tab-" + language).show();
$(".lang-specific." + language).show();

global.toc.calculateHeights();
window.recacheHeights();

// scroll to the new location of the position
if ($(window.location.hash).get(0)) {
Expand Down Expand Up @@ -159,8 +160,5 @@ under the License.
activateLanguage(language);
return false;
});
window.onpopstate = function() {
activateLanguage(getLanguageFromQueryString());
};
});
})(window);
})();
2 changes: 1 addition & 1 deletion source/javascripts/app/_search.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//= require ../lib/_lunr
//= require ../lib/_jquery
//= require ../lib/_jquery.highlight
(function () {
;(function () {
'use strict';

var content, searchResults;
Expand Down
131 changes: 90 additions & 41 deletions source/javascripts/app/_toc.js
Original file line number Diff line number Diff line change
@@ -1,57 +1,106 @@
//= require ../lib/_jquery
//= require ../lib/_jquery_ui
//= require ../lib/_jquery.tocify
//= require ../lib/_imagesloaded.min
(function (global) {
;(function () {
'use strict';

var debounce = function(func, waitTime) {
var timeout = false;
return function() {
if (timeout === false) {
setTimeout(function() {
func();
timeout = false;
}, waitTime);
timeout = true;
}
};
};

var closeToc = function() {
$(".tocify-wrapper").removeClass('open');
$("#nav-button").removeClass('open');
};

var makeToc = function() {
global.toc = $("#toc").tocify({
selectors: 'h1, h2',
extendPage: false,
theme: 'none',
smoothScroll: false,
showEffectSpeed: 0,
hideEffectSpeed: 180,
ignoreSelector: '.toc-ignore',
highlightOffset: 60,
scrollTo: -1,
scrollHistory: true,
hashGenerator: function (text, element) {
return element.prop('id');
function loadToc($toc, tocLinkSelector, tocListSelector, scrollOffset) {
var headerHeights = {};
var pageHeight = 0;
var windowHeight = 0;
var originalTitle = document.title;

var recacheHeights = function() {
headerHeights = {};
pageHeight = $(document).height();
windowHeight = $(window).height();

$toc.find(tocLinkSelector).each(function() {
var targetId = $(this).attr('href');
if (targetId[0] === "#") {
headerHeights[targetId] = $(targetId).offset().top;
}
});
};

var refreshToc = function() {
var currentTop = $(document).scrollTop() + scrollOffset;

if (currentTop + windowHeight >= pageHeight) {
// at bottom of page, so just select last header by making currentTop very large
// this fixes the problem where the last header won't ever show as active if its content
// is shorter than the window height
currentTop = pageHeight + 1000;
}

var best = null;
for (var name in headerHeights) {
if ((headerHeights[name] < currentTop && headerHeights[name] > headerHeights[best]) || best === null) {
best = name;
}
}
}).data('toc-tocify');

$("#nav-button").click(function() {
$(".tocify-wrapper").toggleClass('open');
$("#nav-button").toggleClass('open');
return false;
});
var $best = $toc.find("[href='" + best + "']").first();
if (!$best.hasClass("active")) {
$toc.find(".active").removeClass("active");
$best.addClass("active");
$best.parents(tocListSelector).addClass("active");
$best.siblings(tocListSelector).addClass("active");
$toc.find(tocListSelector).filter(":not(.active)").slideUp(150);
$toc.find(tocListSelector).filter(".active").slideDown(150);
if (window.history.pushState) {
window.history.pushState(null, "", best);
}
// TODO remove classnames
document.title = $best.data("title") + " – " + originalTitle;
}
};

$(".page-wrapper").click(closeToc);
$(".tocify-item").click(closeToc);
};
var makeToc = function() {
recacheHeights();
refreshToc();

// Hack to make already open sections to start opened,
// instead of displaying an ugly animation
function animate() {
setTimeout(function() {
toc.setOption('showEffectSpeed', 180);
}, 50);
}
$("#nav-button").click(function() {
$(".toc-wrapper").toggleClass('open');
$("#nav-button").toggleClass('open');
return false;
});
$(".page-wrapper").click(closeToc);
$(".tocify-item").click(closeToc);

// reload immediately after scrolling on toc click
$toc.find(tocLinkSelector).click(function() {
setTimeout(function() {
refreshToc();
}, 0);
});

$(window).scroll(debounce(refreshToc, 200));
$(window).resize(debounce(recacheHeights, 200));
};

$(function() {
makeToc();
animate();
setupLanguages($('body').data('languages'));
$('.content').imagesLoaded( function() {
global.toc.calculateHeights();
});
});
})(window);

window.recacheHeights = recacheHeights;
window.refreshToc = refreshToc;
}

window.loadToc = loadToc;
})();
Loading

2 comments on commit 53e2f23

@garnold
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lord This commit breaks the Deeper Nesting instructions. Is there a way to get h3's to appear in the TOC again?

@lord
Copy link
Member Author

@lord lord commented on 53e2f23 Feb 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call, thanks. I've filed #703 as a reminder to fix the docs before cutting a new release.

Please sign in to comment.