You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

496 lines
16 KiB

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