plugin-bookdown.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. gitbook.require(["gitbook", "lodash", "jQuery"], function(gitbook, _, $) {
  2. var gs = gitbook.storage;
  3. gitbook.events.bind("start", function(e, config) {
  4. // add the Edit button (edit on Github)
  5. var edit = config.edit;
  6. if (edit && edit.link) gitbook.toolbar.createButton({
  7. icon: 'fa fa-edit',
  8. label: edit.text || 'Edit',
  9. position: 'left',
  10. onClick: function(e) {
  11. e.preventDefault();
  12. window.open(edit.link);
  13. }
  14. });
  15. // add the History button (file history on Github)
  16. var history = config.history;
  17. if (history && history.link) gitbook.toolbar.createButton({
  18. icon: 'fa fa-history',
  19. label: history.text || 'History',
  20. position: 'left',
  21. onClick: function(e) {
  22. e.preventDefault();
  23. window.open(history.link);
  24. }
  25. });
  26. // add the View button (file view on Github)
  27. var view = config.view;
  28. if (view && view.link) gitbook.toolbar.createButton({
  29. icon: 'fa fa-eye',
  30. label: view.text || 'View Source',
  31. position: 'left',
  32. onClick: function(e) {
  33. e.preventDefault();
  34. window.open(view.link);
  35. }
  36. });
  37. // add the Download button
  38. var down = config.download;
  39. var normalizeDownload = function() {
  40. if (!down || !(down instanceof Array) || down.length === 0) return;
  41. if (down[0] instanceof Array) return down;
  42. return $.map(down, function(file, i) {
  43. return [[file, file.replace(/.*[.]/g, '').toUpperCase()]];
  44. });
  45. };
  46. down = normalizeDownload(down);
  47. if (down) if (down.length === 1 && /[.]pdf$/.test(down[0][0])) {
  48. gitbook.toolbar.createButton({
  49. icon: 'fa fa-file-pdf-o',
  50. label: down[0][1],
  51. position: 'left',
  52. onClick: function(e) {
  53. e.preventDefault();
  54. window.open(down[0][0]);
  55. }
  56. });
  57. } else {
  58. gitbook.toolbar.createButton({
  59. icon: 'fa fa-download',
  60. label: 'Download',
  61. position: 'left',
  62. dropdown: $.map(down, function(item, i) {
  63. return {
  64. text: item[1],
  65. onClick: function(e) {
  66. e.preventDefault();
  67. window.open(item[0]);
  68. }
  69. };
  70. })
  71. });
  72. }
  73. // add the Information button
  74. var info = ['Keyboard shortcuts (<> indicates arrow keys):',
  75. '<left>/<right>: navigate to previous/next page',
  76. 's: Toggle sidebar'];
  77. if (config.search !== false) info.push('f: Toggle search input ' +
  78. '(use <up>/<down>/Enter in the search input to navigate through search matches; ' +
  79. 'press Esc to cancel search)');
  80. if (config.info !== false) gitbook.toolbar.createButton({
  81. icon: 'fa fa-info',
  82. label: 'Information about the toolbar',
  83. position: 'left',
  84. onClick: function(e) {
  85. e.preventDefault();
  86. window.alert(info.join('\n\n'));
  87. }
  88. });
  89. // highlight the current section in TOC
  90. var href = window.location.pathname;
  91. href = href.substr(href.lastIndexOf('/') + 1);
  92. // accentuated characters need to be decoded (#819)
  93. href = decodeURIComponent(href);
  94. if (href === '') href = 'index.html';
  95. var li = $('a[href^="' + href + location.hash + '"]').parent('li.chapter').first();
  96. var summary = $('ul.summary'), chaps = summary.find('li.chapter');
  97. if (li.length === 0) li = chaps.first();
  98. li.addClass('active');
  99. chaps.on('click', function(e) {
  100. chaps.removeClass('active');
  101. $(this).addClass('active');
  102. gs.set('tocScrollTop', summary.scrollTop());
  103. });
  104. var toc = config.toc;
  105. // collapse TOC items that are not for the current chapter
  106. if (toc && toc.collapse) (function() {
  107. var type = toc.collapse;
  108. if (type === 'none') return;
  109. if (type !== 'section' && type !== 'subsection') return;
  110. // sections under chapters
  111. var toc_sub = summary.children('li[data-level]').children('ul');
  112. if (type === 'section') {
  113. toc_sub.hide()
  114. .parent().has(li).children('ul').show();
  115. } else {
  116. toc_sub.children('li').children('ul').hide()
  117. .parent().has(li).children('ul').show();
  118. }
  119. li.children('ul').show();
  120. var toc_sub2 = toc_sub.children('li');
  121. if (type === 'section') toc_sub2.children('ul').hide();
  122. summary.children('li[data-level]').find('a')
  123. .on('click.bookdown', function(e) {
  124. if (href === $(this).attr('href').replace(/#.*/, ''))
  125. $(this).parent('li').children('ul').toggle();
  126. });
  127. })();
  128. // add tooltips to the <a>'s that are truncated
  129. $('a').each(function(i, el) {
  130. if (el.offsetWidth >= el.scrollWidth) return;
  131. if (typeof el.title === 'undefined') return;
  132. el.title = el.text;
  133. });
  134. // restore TOC scroll position
  135. var pos = gs.get('tocScrollTop');
  136. if (typeof pos !== 'undefined') summary.scrollTop(pos);
  137. // highlight the TOC item that has same text as the heading in view as scrolling
  138. if (toc && toc.scroll_highlight !== false) (function() {
  139. // scroll the current TOC item into viewport
  140. var ht = $(window).height(), rect = li[0].getBoundingClientRect();
  141. if (rect.top >= ht || rect.top <= 0 || rect.bottom <= 0) {
  142. summary.scrollTop(li[0].offsetTop);
  143. }
  144. // current chapter TOC items
  145. var items = $('a[href^="' + href + '"]').parent('li.chapter'),
  146. m = items.length;
  147. if (m === 0) {
  148. items = summary.find('li.chapter');
  149. m = items.length;
  150. }
  151. if (m === 0) return;
  152. // all section titles on current page
  153. var hs = bookInner.find('.page-inner').find('h1,h2,h3'), n = hs.length,
  154. ts = hs.map(function(i, el) { return $(el).text(); });
  155. if (n === 0) return;
  156. var scrollHandler = function(e) {
  157. var ht = $(window).height();
  158. clearTimeout($.data(this, 'scrollTimer'));
  159. $.data(this, 'scrollTimer', setTimeout(function() {
  160. // find the first visible title in the viewport
  161. for (var i = 0; i < n; i++) {
  162. var rect = hs[i].getBoundingClientRect();
  163. if (rect.top >= 0 && rect.bottom <= ht) break;
  164. }
  165. if (i === n) return;
  166. items.removeClass('active');
  167. for (var j = 0; j < m; j++) {
  168. if (items.eq(j).children('a').first().text() === ts[i]) break;
  169. }
  170. if (j === m) j = 0; // highlight the chapter title
  171. // search bottom-up for a visible TOC item to highlight; if an item is
  172. // hidden, we check if its parent is visible, and so on
  173. while (j > 0 && items.eq(j).is(':hidden')) j--;
  174. items.eq(j).addClass('active');
  175. }, 250));
  176. };
  177. bookInner.on('scroll.bookdown', scrollHandler);
  178. bookBody.on('scroll.bookdown', scrollHandler);
  179. })();
  180. // do not refresh the page if the TOC item points to the current page
  181. $('a[href="' + href + '"]').parent('li.chapter').children('a')
  182. .on('click', function(e) {
  183. bookInner.scrollTop(0);
  184. bookBody.scrollTop(0);
  185. return false;
  186. });
  187. var toolbar = config.toolbar;
  188. if (!toolbar || toolbar.position !== 'static') {
  189. var bookHeader = $('.book-header');
  190. bookBody.addClass('fixed');
  191. bookHeader.addClass('fixed')
  192. .css('background-color', bookBody.css('background-color'))
  193. .on('click.bookdown', function(e) {
  194. // the theme may have changed after user clicks the theme button
  195. bookHeader.css('background-color', bookBody.css('background-color'));
  196. });
  197. }
  198. });
  199. gitbook.events.bind("page.change", function(e) {
  200. // store TOC scroll position
  201. var summary = $('ul.summary');
  202. gs.set('tocScrollTop', summary.scrollTop());
  203. });
  204. var bookBody = $('.book-body'), bookInner = bookBody.find('.body-inner');
  205. var chapterTitle = function() {
  206. return bookInner.find('.page-inner').find('h1,h2').first().text();
  207. };
  208. var saveScrollPos = function(e) {
  209. // save scroll position before page is reloaded
  210. gs.set('bodyScrollTop', {
  211. body: bookBody.scrollTop(),
  212. inner: bookInner.scrollTop(),
  213. focused: document.hasFocus(),
  214. title: chapterTitle()
  215. });
  216. };
  217. $(document).on('servr:reload', saveScrollPos);
  218. // check if the page is loaded in an iframe (e.g. the RStudio preview window)
  219. var inIFrame = function() {
  220. var inIframe = true;
  221. try { inIframe = window.self !== window.top; } catch (e) {}
  222. return inIframe;
  223. };
  224. if (inIFrame()) {
  225. $(window).on('blur unload', saveScrollPos);
  226. }
  227. $(function(e) {
  228. var pos = gs.get('bodyScrollTop');
  229. if (pos) {
  230. if (pos.title === chapterTitle()) {
  231. if (pos.body !== 0) bookBody.scrollTop(pos.body);
  232. if (pos.inner !== 0) bookInner.scrollTop(pos.inner);
  233. }
  234. }
  235. if ((pos && pos.focused) || !inIFrame()) bookInner.find('.page-wrapper').focus();
  236. // clear book body scroll position
  237. gs.remove('bodyScrollTop');
  238. });
  239. });