gitbook-plugin-folding-menu
Version:
GitBook plugin that tames large left-nav menus by visualizing one section at a time
222 lines (193 loc) • 9.15 kB
JavaScript
//***
//*** gitbook-plugin-folding-menu:
//*** GitBook plugin that tames large left-nav menus
//*** by visualizing one section at a time.
//***
require(["gitbook", "jQuery"], function(gitbook, $) {
// our plugin configuration object
// ... optionally defined in client book.json
// ... auto defaulted by gitbook as follows:
// config: {
// animationDuration: 400,
// sticky: false
// }
var config = null;
// glean plugin configuration on start event
gitbook.events.bind('start', function(e, myConfig) {
// simply retain in parent scope (for subsequent use)
config = myConfig['folding-menu'];
diag('start event ... config: ', config);
});
// listen for gitbook "page.change" events
// ... emitted whenever a file.md changes
gitbook.events.bind("page.change", function(e) {
// handle obsecure case where 'active' class designation is delayed
// - either when different MD files are used within sections/sub-sections
// - or when api.md is referenced in multiple seperate sections
// ... i.e. Core API and Extension API
// > process on next event tick (i.e. timeout) seems to help
setTimeout( function() {
diag('page.change event ... e: ', e);
// locate our top level sections
// ... NOTE: this must be inside our event processor.
// - We cannot perform this once statically,
// - BECAUSE, each page change in gitbook is a FULL page change!
// causing a fresh new DOM to be rendered!
var $topNav = $('ul.summary');
diag('$topNav: ', $topNav);
var $topSections = $topNav.children('li.chapter').children('ul.articles');
diag('$topSections length(' + $topSections.length + ') ... ', $topSections);
// locate activeChapter
// ... there will always be only one (defaults to the first)
// ... this is what is visually highlighted
// ... can be at any depth
// >>> KEY:
// <li class="chapter"> at a file break (can be lower down)
var $activeChapter = $('li.chapter.active'); // various renditions ... all seem to work
// var $activeChapter = $('li.active');
// var $activeChapter = $('.active');
diag('$activeChapter length(' + $activeChapter.length + ') ... ', $activeChapter);
// locate our active top-level section
var $activeTopSection = locateActiveTopSection($activeChapter);
diag('$activeTopSection length(' + $activeTopSection.length + ') ... ', $activeTopSection);
// for our animation to work correctly, we must baseline our
// prior section visibility (INSTANTLY - i.e. no animation)
// ... BECAUSE, each page change in gitbook is a FULL page change,
// the entire page is re-displayed, so a page change ALWAYS
// starts out with leftNav expanded!!!
baselinePriorVisibility($topSections);
// when necessary, leave the last section expanded
// ... by simply no-oping
if (config.sticky && // when configured to be sticky -AND-
$activeTopSection.length === 0) { // the current active section has NO sub-content
return;
}
// sync our left-nav to display ONLY the active top section
// ... FINALLY: this is what we are here for :-)
// ... we itterate over each, applying show/hide directives
// as opposed to sledge hammer
// a: hide all top,
// b: show active
// ... this has a MUCH better visual
// ... we also use animation - THANK YOU jQuery!!
var activeTopSectionElm = $activeTopSection.get(0); // undefined if out-of-bounds
$topSections.each( function(indx, topSectionElm) {
if (topSectionElm === activeTopSectionElm) {
setVisible(topSectionElm, 'show');
}
else {
setVisible(topSectionElm, 'hide');
}
});
}, 0); // TIMEOUT for next event tick
});
// cache retaining current visibility of sections
// - NOTE: We cannot retain this state in the DOM
// via jQuery.data()
// BECAUSE, each page change in gitbook is a FULL page change!
// In other words, the entire page is re-displayed
// so a page change ALWAYS starts out with leftNav expanded!!!
// THEREFORE: we maintain our own cache, keyed by the data-path
// maintained in the DOM by gitbook.
var visibilityCache = {
/* dynamically maintained ... ex:
elmSectionKey: 'show'/'hide'
============== =============
'usage.html': 'show'
'detail.html': 'hide'
*/
};
// baseline our prior section visibility (INSTANTLY - i.e. no animation)
// ... BECAUSE, each page change in gitbook is a FULL page change,
// the entire page is re-displayed, so a page change ALWAYS
// starts out with leftNav expanded!!!
function baselinePriorVisibility($topSections) {
$topSections.each( function(indx, topSectionElm) {
var sectionKey = getSectionKey(topSectionElm);
var sectionVisibility = getCurSectionVisibility(sectionKey);
if (sectionVisibility === 'show') {
$(topSectionElm).show(); // NO animation on baseline
}
else {
$(topSectionElm).hide(); // NO animation on baseline
}
});
}
// return the persistent section key for the supplied elmSection (ex: "detail.html")
// ... sample elmSection DOM expection (from gitbook generation of leftNav)
// <ul>
// <li data-path="detail.html"> ... we hang our hat on this data-path (maintained by gitbook)
// <li data-path="detail.html#someHash">
// <li data-path="detail.html#etc">
// </ul>
function getSectionKey(elmSection) { // 'show'/'hide'
var domKey = 'data-path'; // maintained by gitbook (something unique to hang our hat on)
var sectionKey = elmSection.childNodes[1].getAttribute(domKey); // HACK: a bit vulnerable ... [0] is a text node, [1] is ... ex: 'detail.html'
return sectionKey;
}
// return the current visibilitys of the supplied sectionKey (from our cache)
function getCurSectionVisibility(sectionKey) { // 'show'/'hide'
var curVisibility = visibilityCache[sectionKey];
return curVisibility || 'hide'; // we force the default/initial state bo be hidden (with our baseline process - an initial display will collapse all sections - but the active)
}
// convenience routine to show/hide supplied elmSection
// ... keeping track of current visibility state
// - resulting in a MUCH better visual
function setVisible(elmSection, directive) {
var sectionKey = getSectionKey(elmSection);
var curSectionVisibility = getCurSectionVisibility(sectionKey);
var diagMsg = 'setVisible(elmSection: ' + sectionKey + ', directive: ' + directive + ') ... curSectionVisibility: ' + curSectionVisibility + ' ... ';
// apply the visibility directive, when needed (based on cached current visibility)
if (directive === 'show') {
if (curSectionVisibility !== 'show') { // when out-of-sync
$(elmSection).show(config.animationDuration); // ... change visiblity WITH animation
visibilityCache[sectionKey] = 'show'; // ... maintaining our cache
diagMsg += 'SHOWING';
}
else {
diagMsg += 'no-oping';
}
}
else {
if (curSectionVisibility !== 'hide') { // when out-of-sync
$(elmSection).hide(config.animationDuration); // ... change visiblity WITH animation
visibilityCache[sectionKey] = 'hide'; // ... maintaining our cache
diagMsg += 'HIDING';
}
else {
diagMsg += 'no-oping';
}
}
diag(diagMsg);
}
// locate top level chapter
// ... drill up till parent is <ul class="summary">
function topChapter($elm) {
// normally the initial supplied $elm is what we want
// ... however, when a sub-section is part of a separate file (e.g. api.md under coreApi.md)
// then the initial $elm is lower, and we must drill up (recursively)
if ($elm.parent().attr('class') !== 'summary' &&
$elm.length !== 0) {
diag('topChapter UP');
return topChapter($elm.parent());
}
else {
diag('topChapter USE: ', $elm);
return $elm;
}
}
// locate our active top section
function locateActiveTopSection($activeChapter) {
// drill up till parent is <ul class="summary">
var $topChapter = topChapter($activeChapter);
// resolve chapter into section (if any)
// ... will be an item of 0 length if there is no section
return $topChapter.children('ul.articles');
}
// convenience diagnostic logger
function diag(msg, obj) {
return; // comment out to enable
console.log('Manage LeftNav: ' + msg,
obj ? obj : '');
}
});