learn.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. // Scrollbar Width function
  2. function getScrollBarWidth() {
  3. var inner = document.createElement('p');
  4. inner.style.width = "100%";
  5. inner.style.height = "200px";
  6. var outer = document.createElement('div');
  7. outer.style.position = "absolute";
  8. outer.style.top = "0px";
  9. outer.style.left = "0px";
  10. outer.style.visibility = "hidden";
  11. outer.style.width = "200px";
  12. outer.style.height = "150px";
  13. outer.style.overflow = "hidden";
  14. outer.appendChild(inner);
  15. document.body.appendChild(outer);
  16. var w1 = inner.offsetWidth;
  17. outer.style.overflow = 'scroll';
  18. var w2 = inner.offsetWidth;
  19. if (w1 == w2) w2 = outer.clientWidth;
  20. document.body.removeChild(outer);
  21. return (w1 - w2);
  22. };
  23. function setMenuHeight() {
  24. $('#sidebar .highlightable').height($('#sidebar').innerHeight() - $('#header-wrapper').height() - 40);
  25. $('#sidebar .highlightable').perfectScrollbar('update');
  26. }
  27. function fallbackMessage(action) {
  28. var actionMsg = '';
  29. var actionKey = (action === 'cut' ? 'X' : 'C');
  30. if (/iPhone|iPad/i.test(navigator.userAgent)) {
  31. actionMsg = 'No support :(';
  32. }
  33. else if (/Mac/i.test(navigator.userAgent)) {
  34. actionMsg = 'Press ⌘-' + actionKey + ' to ' + action;
  35. }
  36. else {
  37. actionMsg = 'Press Ctrl-' + actionKey + ' to ' + action;
  38. }
  39. return actionMsg;
  40. }
  41. // for the window resize
  42. $(window).resize(function() {
  43. setMenuHeight();
  44. });
  45. // debouncing function from John Hann
  46. // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
  47. (function($, sr) {
  48. var debounce = function(func, threshold, execAsap) {
  49. var timeout;
  50. return function debounced() {
  51. var obj = this, args = arguments;
  52. function delayed() {
  53. if (!execAsap)
  54. func.apply(obj, args);
  55. timeout = null;
  56. };
  57. if (timeout)
  58. clearTimeout(timeout);
  59. else if (execAsap)
  60. func.apply(obj, args);
  61. timeout = setTimeout(delayed, threshold || 100);
  62. };
  63. }
  64. // smartresize
  65. jQuery.fn[sr] = function(fn) { return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr); };
  66. })(jQuery, 'smartresize');
  67. jQuery(document).ready(function() {
  68. jQuery('#sidebar .category-icon').on('click', function() {
  69. $( this ).toggleClass("fa-angle-down fa-angle-right") ;
  70. $( this ).parent().parent().children('ul').toggle() ;
  71. return false;
  72. });
  73. var sidebarStatus = searchStatus = 'open';
  74. $('#sidebar .highlightable').perfectScrollbar();
  75. setMenuHeight();
  76. jQuery('#overlay').on('click', function() {
  77. jQuery(document.body).toggleClass('sidebar-hidden');
  78. sidebarStatus = (jQuery(document.body).hasClass('sidebar-hidden') ? 'closed' : 'open');
  79. return false;
  80. });
  81. jQuery('[data-sidebar-toggle]').on('click', function() {
  82. jQuery(document.body).toggleClass('sidebar-hidden');
  83. sidebarStatus = (jQuery(document.body).hasClass('sidebar-hidden') ? 'closed' : 'open');
  84. return false;
  85. });
  86. jQuery('[data-clear-history-toggle]').on('click', function() {
  87. sessionStorage.clear();
  88. location.reload();
  89. return false;
  90. });
  91. jQuery('[data-search-toggle]').on('click', function() {
  92. if (sidebarStatus == 'closed') {
  93. jQuery('[data-sidebar-toggle]').trigger('click');
  94. jQuery(document.body).removeClass('searchbox-hidden');
  95. searchStatus = 'open';
  96. return false;
  97. }
  98. jQuery(document.body).toggleClass('searchbox-hidden');
  99. searchStatus = (jQuery(document.body).hasClass('searchbox-hidden') ? 'closed' : 'open');
  100. return false;
  101. });
  102. var ajax;
  103. jQuery('[data-search-input]').on('input', function() {
  104. var input = jQuery(this),
  105. value = input.val(),
  106. items = jQuery('[data-nav-id]');
  107. items.removeClass('search-match');
  108. if (!value.length) {
  109. $('ul.topics').removeClass('searched');
  110. items.css('display', 'block');
  111. sessionStorage.removeItem('search-value');
  112. $(".highlightable").unhighlight({ element: 'mark' })
  113. return;
  114. }
  115. sessionStorage.setItem('search-value', value);
  116. $(".highlightable").unhighlight({ element: 'mark' }).highlight(value, { element: 'mark' });
  117. if (ajax && ajax.abort) ajax.abort();
  118. jQuery('[data-search-clear]').on('click', function() {
  119. jQuery('[data-search-input]').val('').trigger('input');
  120. sessionStorage.removeItem('search-input');
  121. $(".highlightable").unhighlight({ element: 'mark' })
  122. });
  123. });
  124. $.expr[":"].contains = $.expr.createPseudo(function(arg) {
  125. return function( elem ) {
  126. return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
  127. };
  128. });
  129. if (sessionStorage.getItem('search-value')) {
  130. var searchValue = sessionStorage.getItem('search-value')
  131. $(document.body).removeClass('searchbox-hidden');
  132. $('[data-search-input]').val(searchValue);
  133. $('[data-search-input]').trigger('input');
  134. var searchedElem = $('#body-inner').find(':contains(' + searchValue + ')').get(0);
  135. if (searchedElem) {
  136. searchedElem.scrollIntoView(true);
  137. var scrolledY = window.scrollY;
  138. if(scrolledY){
  139. window.scroll(0, scrolledY - 125);
  140. }
  141. }
  142. }
  143. // clipboard
  144. var clipInit = false;
  145. $('code').each(function() {
  146. var code = $(this),
  147. text = code.text();
  148. if (text.length > 5) {
  149. if (!clipInit) {
  150. var text, clip = new Clipboard('.copy-to-clipboard', {
  151. text: function(trigger) {
  152. text = $(trigger).prev('code').text();
  153. return text.replace(/^\$\s/gm, '');
  154. }
  155. });
  156. var inPre;
  157. clip.on('success', function(e) {
  158. e.clearSelection();
  159. inPre = $(e.trigger).parent().prop('tagName') == 'PRE';
  160. $(e.trigger).attr('aria-label', 'Copied to clipboard!').addClass('tooltipped tooltipped-' + (inPre ? 'w' : 's'));
  161. });
  162. clip.on('error', function(e) {
  163. inPre = $(e.trigger).parent().prop('tagName') == 'PRE';
  164. $(e.trigger).attr('aria-label', fallbackMessage(e.action)).addClass('tooltipped tooltipped-' + (inPre ? 'w' : 's'));
  165. $(document).one('copy', function(){
  166. $(e.trigger).attr('aria-label', 'Copied to clipboard!').addClass('tooltipped tooltipped-' + (inPre ? 'w' : 's'));
  167. });
  168. });
  169. clipInit = true;
  170. }
  171. code.after('<span class="copy-to-clipboard" title="Copy to clipboard" />');
  172. code.next('.copy-to-clipboard').on('mouseleave', function() {
  173. $(this).attr('aria-label', null).removeClass('tooltipped tooltipped-s tooltipped-w');
  174. });
  175. }
  176. });
  177. // allow keyboard control for prev/next links
  178. jQuery(function() {
  179. jQuery('.nav-prev').click(function(){
  180. location.href = jQuery(this).attr('href');
  181. });
  182. jQuery('.nav-next').click(function() {
  183. location.href = jQuery(this).attr('href');
  184. });
  185. });
  186. jQuery('input, textarea').keydown(function (e) {
  187. // left and right arrow keys
  188. if (e.which == '37' || e.which == '39') {
  189. e.stopPropagation();
  190. }
  191. });
  192. jQuery(document).keydown(function(e) {
  193. // prev links - left arrow key
  194. if(e.which == '37') {
  195. jQuery('.nav.nav-prev').click();
  196. }
  197. // next links - right arrow key
  198. if(e.which == '39') {
  199. jQuery('.nav.nav-next').click();
  200. }
  201. });
  202. $('#top-bar a:not(:has(img)):not(.btn)').addClass('highlight');
  203. $('#body-inner a:not(:has(img)):not(.btn):not(a[rel="footnote"])').addClass('highlight');
  204. var touchsupport = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)
  205. if (!touchsupport){ // browser doesn't support touch
  206. $('#toc-menu').hover(function() {
  207. $('.progress').stop(true, false, true).fadeToggle(100);
  208. });
  209. $('.progress').hover(function() {
  210. $('.progress').stop(true, false, true).fadeToggle(100);
  211. });
  212. }
  213. if (touchsupport){ // browser does support touch
  214. $('#toc-menu').click(function() {
  215. $('.progress').stop(true, false, true).fadeToggle(100);
  216. });
  217. $('.progress').click(function() {
  218. $('.progress').stop(true, false, true).fadeToggle(100);
  219. });
  220. }
  221. /**
  222. * Fix anchor scrolling that hides behind top nav bar
  223. * Courtesy of https://stackoverflow.com/a/13067009/28106
  224. *
  225. * We could use pure css for this if only heading anchors were
  226. * involved, but this works for any anchor, including footnotes
  227. **/
  228. (function (document, history, location) {
  229. var HISTORY_SUPPORT = !!(history && history.pushState);
  230. var anchorScrolls = {
  231. ANCHOR_REGEX: /^#[^ ]+$/,
  232. OFFSET_HEIGHT_PX: 50,
  233. /**
  234. * Establish events, and fix initial scroll position if a hash is provided.
  235. */
  236. init: function () {
  237. this.scrollToCurrent();
  238. $(window).on('hashchange', $.proxy(this, 'scrollToCurrent'));
  239. $('body').on('click', 'a', $.proxy(this, 'delegateAnchors'));
  240. },
  241. /**
  242. * Return the offset amount to deduct from the normal scroll position.
  243. * Modify as appropriate to allow for dynamic calculations
  244. */
  245. getFixedOffset: function () {
  246. return this.OFFSET_HEIGHT_PX;
  247. },
  248. /**
  249. * If the provided href is an anchor which resolves to an element on the
  250. * page, scroll to it.
  251. * @param {String} href
  252. * @return {Boolean} - Was the href an anchor.
  253. */
  254. scrollIfAnchor: function (href, pushToHistory) {
  255. var match, anchorOffset;
  256. if (!this.ANCHOR_REGEX.test(href)) {
  257. return false;
  258. }
  259. match = document.getElementById(href.slice(1));
  260. if (match) {
  261. anchorOffset = $(match).offset().top - this.getFixedOffset();
  262. $('html, body').animate({ scrollTop: anchorOffset });
  263. // Add the state to history as-per normal anchor links
  264. if (HISTORY_SUPPORT && pushToHistory) {
  265. history.pushState({}, document.title, location.pathname + href);
  266. }
  267. }
  268. return !!match;
  269. },
  270. /**
  271. * Attempt to scroll to the current location's hash.
  272. */
  273. scrollToCurrent: function (e) {
  274. if (this.scrollIfAnchor(window.location.hash) && e) {
  275. e.preventDefault();
  276. }
  277. },
  278. /**
  279. * If the click event's target was an anchor, fix the scroll position.
  280. */
  281. delegateAnchors: function (e) {
  282. var elem = e.target;
  283. if (this.scrollIfAnchor(elem.getAttribute('href'), true)) {
  284. e.preventDefault();
  285. }
  286. }
  287. };
  288. $(document).ready($.proxy(anchorScrolls, 'init'));
  289. })(window.document, window.history, window.location);
  290. });
  291. jQuery(window).on('load', function() {
  292. function adjustForScrollbar() {
  293. if ((parseInt(jQuery('#body-inner').height()) + 83) >= jQuery('#body').height()) {
  294. jQuery('.nav.nav-next').css({ 'margin-right': getScrollBarWidth() });
  295. } else {
  296. jQuery('.nav.nav-next').css({ 'margin-right': 0 });
  297. }
  298. }
  299. // adjust sidebar for scrollbar
  300. adjustForScrollbar();
  301. jQuery(window).smartresize(function() {
  302. adjustForScrollbar();
  303. });
  304. // store this page in session
  305. sessionStorage.setItem(jQuery('body').data('url'), 1);
  306. // loop through the sessionStorage and see if something should be marked as visited
  307. for (var url in sessionStorage) {
  308. if (sessionStorage.getItem(url) == 1) jQuery('[data-nav-id="' + url + '"]').addClass('visited');
  309. }
  310. $(".highlightable").highlight(sessionStorage.getItem('search-value'), { element: 'mark' });
  311. });
  312. $(function() {
  313. $('a[rel="lightbox"]').featherlight({
  314. root: 'section#body'
  315. });
  316. });
  317. jQuery.extend({
  318. highlight: function(node, re, nodeName, className) {
  319. if (node.nodeType === 3) {
  320. var match = node.data.match(re);
  321. if (match) {
  322. var highlight = document.createElement(nodeName || 'span');
  323. highlight.className = className || 'highlight';
  324. var wordNode = node.splitText(match.index);
  325. wordNode.splitText(match[0].length);
  326. var wordClone = wordNode.cloneNode(true);
  327. highlight.appendChild(wordClone);
  328. wordNode.parentNode.replaceChild(highlight, wordNode);
  329. return 1; //skip added node in parent
  330. }
  331. } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
  332. !/(script|style)/i.test(node.tagName) && // ignore script and style nodes
  333. !(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted
  334. for (var i = 0; i < node.childNodes.length; i++) {
  335. i += jQuery.highlight(node.childNodes[i], re, nodeName, className);
  336. }
  337. }
  338. return 0;
  339. }
  340. });
  341. jQuery.fn.unhighlight = function(options) {
  342. var settings = {
  343. className: 'highlight',
  344. element: 'span'
  345. };
  346. jQuery.extend(settings, options);
  347. return this.find(settings.element + "." + settings.className).each(function() {
  348. var parent = this.parentNode;
  349. parent.replaceChild(this.firstChild, this);
  350. parent.normalize();
  351. }).end();
  352. };
  353. jQuery.fn.highlight = function(words, options) {
  354. var settings = {
  355. className: 'highlight',
  356. element: 'span',
  357. caseSensitive: false,
  358. wordsOnly: false
  359. };
  360. jQuery.extend(settings, options);
  361. if (!words) { return; }
  362. if (words.constructor === String) {
  363. words = [words];
  364. }
  365. words = jQuery.grep(words, function(word, i) {
  366. return word != '';
  367. });
  368. words = jQuery.map(words, function(word, i) {
  369. return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
  370. });
  371. if (words.length == 0) { return this; }
  372. ;
  373. var flag = settings.caseSensitive ? "" : "i";
  374. var pattern = "(" + words.join("|") + ")";
  375. if (settings.wordsOnly) {
  376. pattern = "\\b" + pattern + "\\b";
  377. }
  378. var re = new RegExp(pattern, flag);
  379. return this.each(function() {
  380. jQuery.highlight(this, re, settings.element, settings.className);
  381. });
  382. };