|
| 1 | +//*** |
| 2 | +//*** gitbook-plugin-folding-menu: |
| 3 | +//*** GitBook plugin that tames large left-nav menus |
| 4 | +//*** by visualizing one section at a time. |
| 5 | +//*** |
| 6 | +require(["gitbook", "jQuery"], function(gitbook, $) { |
| 7 | + |
| 8 | + // listen for gitbook "page.change" events |
| 9 | + // ... emitted whenever a file.md changes |
| 10 | + gitbook.events.bind("page.change", function(e) { |
| 11 | + |
| 12 | + // handle obsecure case where 'active' class designation is delayed |
| 13 | + // - either when different MD files are used within sections/sub-sections |
| 14 | + // - or when api.md is referenced in multiple seperate sections |
| 15 | + // ... i.e. Core API and Extension API |
| 16 | + // > process on next event tick (i.e. timeout) seems to help |
| 17 | + setTimeout( function() { |
| 18 | + |
| 19 | + diag('page.change event ... e: ', e); |
| 20 | + |
| 21 | + // locate our top level sections |
| 22 | + // ... NOTE: this must be inside our event processor. |
| 23 | + // - We cannot perform this once statically, |
| 24 | + // - BECAUSE, each page change in gitbook is a FULL page change! |
| 25 | + // causing a fresh new DOM to be rendered! |
| 26 | + var $topNav = $('ul.summary'); |
| 27 | + diag('$topNav: ', $topNav); |
| 28 | + var $topSections = $topNav.children('li.chapter').children('ul.articles'); |
| 29 | + diag('$topSections length(' + $topSections.length + ') ... ', $topSections); |
| 30 | + |
| 31 | + // locate activeChapter |
| 32 | + // ... there will always be only one (defaults to the first) |
| 33 | + // ... this is what is visually highlighted |
| 34 | + // ... can be at any depth |
| 35 | + // >>> KEY: |
| 36 | + // <li class="chapter"> at a file break (can be lower down) |
| 37 | + var $activeChapter = $('li.chapter.active'); // various renditions ... all seem to work |
| 38 | + // var $activeChapter = $('li.active'); |
| 39 | + // var $activeChapter = $('.active'); |
| 40 | + diag('$activeChapter length(' + $activeChapter.length + ') ... ', $activeChapter); |
| 41 | + |
| 42 | + // locate our active top-level section |
| 43 | + var $activeTopSection = locateActiveTopSection($activeChapter); |
| 44 | + diag('$activeTopSection length(' + $activeTopSection.length + ') ... ', $activeTopSection); |
| 45 | + |
| 46 | + // for our animation to work correctly, we must baseline our |
| 47 | + // prior section visibility (INSTANTLY - i.e. no animation) |
| 48 | + // ... BECAUSE, each page change in gitbook is a FULL page change, |
| 49 | + // the entire page is re-displayed, so a page change ALWAYS |
| 50 | + // starts out with leftNav expanded!!! |
| 51 | + baselinePriorVisibility($topSections); |
| 52 | + |
| 53 | + // leave the last expanded section in-tact, when a the current section has NO content |
| 54 | + // ... by simply no-oping |
| 55 | + if ($activeTopSection.length === 0) { |
| 56 | + return; |
| 57 | + } |
| 58 | + |
| 59 | + // sync our left-nav to display ONLY the active top section |
| 60 | + // ... FINALLY: this is what we are here for :-) |
| 61 | + // ... we itterate over each, applying show/hide directives |
| 62 | + // as opposed to sledge hammer |
| 63 | + // a: hide all top, |
| 64 | + // b: show active |
| 65 | + // ... this has a MUCH better visual |
| 66 | + // ... we also use animation - THANK YOU jQuery!! |
| 67 | + var activeTopSectionElm = $activeTopSection.get(0); // undefined if out-of-bounds |
| 68 | + $topSections.each( function(indx, topSectionElm) { |
| 69 | + if (topSectionElm === activeTopSectionElm) { |
| 70 | + setVisible(topSectionElm, 'show'); |
| 71 | + } |
| 72 | + else { |
| 73 | + setVisible(topSectionElm, 'hide'); |
| 74 | + } |
| 75 | + }); |
| 76 | + |
| 77 | + }, 0); // TIMEOUT for next event tick |
| 78 | + |
| 79 | + }); |
| 80 | + |
| 81 | + // cache retaining current visibility of sections |
| 82 | + // - NOTE: We cannot retain this state in the DOM |
| 83 | + // via jQuery.data() |
| 84 | + // BECAUSE, each page change in gitbook is a FULL page change! |
| 85 | + // In other words, the entire page is re-displayed |
| 86 | + // so a page change ALWAYS starts out with leftNav expanded!!! |
| 87 | + // THEREFORE: we maintain our own cache, keyed by the data-path |
| 88 | + // maintained in the DOM by gitbook. |
| 89 | + var visibilityCache = { |
| 90 | + /* dynamically maintained ... ex: |
| 91 | + elmSectionKey: 'show'/'hide' |
| 92 | + ============== ============= |
| 93 | + 'usage.html': 'show' |
| 94 | + 'detail.html': 'hide' |
| 95 | + */ |
| 96 | + }; |
| 97 | + |
| 98 | + // baseline our prior section visibility (INSTANTLY - i.e. no animation) |
| 99 | + // ... BECAUSE, each page change in gitbook is a FULL page change, |
| 100 | + // the entire page is re-displayed, so a page change ALWAYS |
| 101 | + // starts out with leftNav expanded!!! |
| 102 | + function baselinePriorVisibility($topSections) { |
| 103 | + $topSections.each( function(indx, topSectionElm) { |
| 104 | + var sectionKey = getSectionKey(topSectionElm); |
| 105 | + var sectionVisibility = getCurSectionVisibility(sectionKey); |
| 106 | + if (sectionVisibility === 'show') { |
| 107 | + $(topSectionElm).show(); // NO animation on baseline |
| 108 | + } |
| 109 | + else { |
| 110 | + $(topSectionElm).hide(); // NO animation on baseline |
| 111 | + } |
| 112 | + }); |
| 113 | + } |
| 114 | + |
| 115 | + // return the persistent section key for the supplied elmSection (ex: "detail.html") |
| 116 | + // ... sample elmSection DOM expection (from gitbook generation of leftNav) |
| 117 | + // <ul> |
| 118 | + // <li data-path="detail.html"> ... we hang our hat on this data-path (maintained by gitbook) |
| 119 | + // <li data-path="detail.html#someHash"> |
| 120 | + // <li data-path="detail.html#etc"> |
| 121 | + // </ul> |
| 122 | + function getSectionKey(elmSection) { // 'show'/'hide' |
| 123 | + var domKey = 'data-path'; // maintained by gitbook (something unique to hang our hat on) |
| 124 | + var sectionKey = elmSection.childNodes[1].getAttribute(domKey); // HACK: a bit vulnerable ... [0] is a text node, [1] is ... ex: 'detail.html' |
| 125 | + return sectionKey; |
| 126 | + } |
| 127 | + |
| 128 | + // return the current visibilitys of the supplied sectionKey (from our cache) |
| 129 | + function getCurSectionVisibility(sectionKey) { // 'show'/'hide' |
| 130 | + var curVisibility = visibilityCache[sectionKey]; |
| 131 | + 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) |
| 132 | + } |
| 133 | + |
| 134 | + // convenience routine to show/hide supplied elmSection |
| 135 | + // ... keeping track of current visibility state |
| 136 | + // - resulting in a MUCH better visual |
| 137 | + function setVisible(elmSection, directive) { |
| 138 | + |
| 139 | + var animationDelay = 400; // utilize an appropriate animation delay (nice visual) |
| 140 | + |
| 141 | + var sectionKey = getSectionKey(elmSection); |
| 142 | + var curSectionVisibility = getCurSectionVisibility(sectionKey); |
| 143 | + |
| 144 | + var diagMsg = 'setVisible(elmSection: ' + sectionKey + ', directive: ' + directive + ') ... curSectionVisibility: ' + curSectionVisibility + ' ... '; |
| 145 | + |
| 146 | + // apply the visibility directive, when needed (based on cached current visibility) |
| 147 | + if (directive === 'show') { |
| 148 | + if (curSectionVisibility !== 'show') { // when out-of-sync |
| 149 | + $(elmSection).show(animationDelay); // ... change visiblity WITH animation |
| 150 | + visibilityCache[sectionKey] = 'show'; // ... maintaining our cache |
| 151 | + diagMsg += 'SHOWING'; |
| 152 | + } |
| 153 | + else { |
| 154 | + diagMsg += 'no-oping'; |
| 155 | + } |
| 156 | + } |
| 157 | + else { |
| 158 | + if (curSectionVisibility !== 'hide') { // when out-of-sync |
| 159 | + $(elmSection).hide(animationDelay); // ... change visiblity WITH animation |
| 160 | + visibilityCache[sectionKey] = 'hide'; // ... maintaining our cache |
| 161 | + diagMsg += 'HIDING'; |
| 162 | + } |
| 163 | + else { |
| 164 | + diagMsg += 'no-oping'; |
| 165 | + } |
| 166 | + } |
| 167 | + |
| 168 | + diag(diagMsg); |
| 169 | + } |
| 170 | + |
| 171 | + // locate top level chapter |
| 172 | + // ... drill up till parent is <ul class="summary"> |
| 173 | + function topChapter($elm) { |
| 174 | + // normally the initial supplied $elm is what we want |
| 175 | + // ... however, when a sub-section is part of a separate file (e.g. api.md under coreApi.md) |
| 176 | + // then the initial $elm is lower, and we must drill up (recursively) |
| 177 | + if ($elm.parent().attr('class') !== 'summary' && |
| 178 | + $elm.length !== 0) { |
| 179 | + diag('topChapter UP'); |
| 180 | + return topChapter($elm.parent()); |
| 181 | + } |
| 182 | + else { |
| 183 | + diag('topChapter USE: ', $elm); |
| 184 | + return $elm; |
| 185 | + } |
| 186 | + } |
| 187 | + |
| 188 | + // locate our active top section |
| 189 | + function locateActiveTopSection($activeChapter) { |
| 190 | + // drill up till parent is <ul class="summary"> |
| 191 | + var $topChapter = topChapter($activeChapter); |
| 192 | + |
| 193 | + // resolve chapter into section (if any) |
| 194 | + // ... will be an item of 0 length if there is no section |
| 195 | + return $topChapter.children('ul.articles'); |
| 196 | + } |
| 197 | + |
| 198 | + // convenience diagnostic logger |
| 199 | + function diag(msg, obj) { |
| 200 | + return; // comment out to enable |
| 201 | + console.log('Manage LeftNav: ' + msg, |
| 202 | + obj ? obj : ''); |
| 203 | + } |
| 204 | + |
| 205 | +}); |
0 commit comments