617 lines
21 KiB
JavaScript
617 lines
21 KiB
JavaScript
// Scrollbar Width function
|
|
function getScrollBarWidth() {
|
|
var inner = document.createElement('p');
|
|
inner.style.width = "100%";
|
|
inner.style.height = "200px";
|
|
|
|
var outer = document.createElement('div');
|
|
outer.style.position = "absolute";
|
|
outer.style.top = "0px";
|
|
outer.style.left = "0px";
|
|
outer.style.visibility = "hidden";
|
|
outer.style.width = "200px";
|
|
outer.style.height = "150px";
|
|
outer.style.overflow = "hidden";
|
|
outer.appendChild(inner);
|
|
|
|
document.body.appendChild(outer);
|
|
var w1 = inner.offsetWidth;
|
|
outer.style.overflow = 'scroll';
|
|
var w2 = inner.offsetWidth;
|
|
if (w1 == w2) w2 = outer.clientWidth;
|
|
|
|
document.body.removeChild(outer);
|
|
|
|
return (w1 - w2);
|
|
};
|
|
|
|
function setMenuHeight() {
|
|
$('#sidebar .highlightable').height($('#sidebar').innerHeight() - $('#header-wrapper').height() - 40);
|
|
$('#sidebar .highlightable').perfectScrollbar('update');
|
|
}
|
|
|
|
function fallbackMessage(action) {
|
|
var actionMsg = '';
|
|
var actionKey = (action === 'cut' ? 'X' : 'C');
|
|
|
|
if (/iPhone|iPad/i.test(navigator.userAgent)) {
|
|
actionMsg = 'No support :(';
|
|
}
|
|
else if (/Mac/i.test(navigator.userAgent)) {
|
|
actionMsg = 'Press ⌘-' + actionKey + ' to ' + action;
|
|
}
|
|
else {
|
|
actionMsg = 'Press Ctrl-' + actionKey + ' to ' + action;
|
|
}
|
|
|
|
return actionMsg;
|
|
}
|
|
|
|
function switchTab(tabGroup, tabId) {
|
|
allTabItems = jQuery("[data-tab-group='"+tabGroup+"']");
|
|
targetTabItems = jQuery("[data-tab-group='"+tabGroup+"'][data-tab-item='"+tabId+"']");
|
|
|
|
// if event is undefined then switchTab was called from restoreTabSelection
|
|
// so it's not a button event and we don't need to safe the selction or
|
|
// prevent page jump
|
|
var isButtonEvent = event != undefined;
|
|
|
|
if(isButtonEvent){
|
|
// save button position relative to viewport
|
|
var yposButton = event.target.getBoundingClientRect().top;
|
|
}
|
|
|
|
allTabItems.removeClass("active");
|
|
targetTabItems.addClass("active");
|
|
|
|
if(isButtonEvent){
|
|
// reset screen to the same position relative to clicked button to prevent page jump
|
|
var yposButtonDiff = event.target.getBoundingClientRect().top - yposButton;
|
|
window.scrollTo(window.scrollX, window.scrollY+yposButtonDiff);
|
|
|
|
// Store the selection to make it persistent
|
|
if(window.localStorage){
|
|
var selectionsJSON = window.localStorage.getItem("tabSelections");
|
|
if(selectionsJSON){
|
|
var tabSelections = JSON.parse(selectionsJSON);
|
|
}else{
|
|
var tabSelections = {};
|
|
}
|
|
tabSelections[tabGroup] = tabId;
|
|
window.localStorage.setItem("tabSelections", JSON.stringify(tabSelections));
|
|
}
|
|
}
|
|
}
|
|
|
|
function restoreTabSelections() {
|
|
if(window.localStorage){
|
|
var selectionsJSON = window.localStorage.getItem("tabSelections");
|
|
if(selectionsJSON){
|
|
var tabSelections = JSON.parse(selectionsJSON);
|
|
}else{
|
|
var tabSelections = {};
|
|
}
|
|
Object.keys(tabSelections).forEach(function(tabGroup) {
|
|
var tabItem = tabSelections[tabGroup];
|
|
switchTab(tabGroup, tabItem);
|
|
});
|
|
}
|
|
}
|
|
|
|
function initMermaid() {
|
|
$('code.language-mermaid').each(function(index, element) {
|
|
var content = $(element).html().replace(/&/g, '&');
|
|
$(element).parent().replaceWith('<div class="mermaid" align="center">' + content + '</div>');
|
|
});
|
|
|
|
if (typeof mermaid != 'undefined') {
|
|
mermaid.mermaidAPI.initialize( Object.assign( {}, mermaid.mermaidAPI.getSiteConfig(), { startOnLoad: true } ) );
|
|
mermaid.contentLoaded();
|
|
$(".mermaid svg").svgPanZoom({})
|
|
}
|
|
}
|
|
|
|
function scrollToActiveMenu() {
|
|
window.setTimeout(function(){
|
|
var e = $("#sidebar ul.topics li.active")[0];
|
|
if( e && e.scrollIntoView ){
|
|
e.scrollIntoView({
|
|
block: 'center',
|
|
});
|
|
}
|
|
}, 200);
|
|
}
|
|
|
|
// Get Parameters from some url
|
|
var getUrlParameter = function getUrlParameter(sPageURL) {
|
|
var url = sPageURL.split('?');
|
|
var obj = {};
|
|
if (url.length == 2) {
|
|
var sURLVariables = url[1].split('&'),
|
|
sParameterName,
|
|
i;
|
|
for (i = 0; i < sURLVariables.length; i++) {
|
|
sParameterName = sURLVariables[i].split('=');
|
|
obj[sParameterName[0]] = sParameterName[1];
|
|
}
|
|
}
|
|
return obj;
|
|
};
|
|
|
|
// Execute actions on images generated from Markdown pages
|
|
var images = $("main#body-inner img").not(".inline");
|
|
// Wrap image inside a featherlight (to get a full size view in a popup)
|
|
images.wrap(function(){
|
|
var image =$(this);
|
|
var o = getUrlParameter(image[0].src);
|
|
var f = o['featherlight'];
|
|
// IF featherlight is false, do not use feather light
|
|
if (f != 'false') {
|
|
if (!image.parent("a").length) {
|
|
var html = $( "<a>" ).attr("href", image[0].src).attr("data-featherlight", "image").get(0).outerHTML;
|
|
return html;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Change styles, depending on parameters set to the image
|
|
images.each(function(index){
|
|
var image = $(this)
|
|
var o = getUrlParameter(image[0].src);
|
|
if (typeof o !== "undefined") {
|
|
var h = o["height"];
|
|
var w = o["width"];
|
|
var c = o["classes"];
|
|
image.css("width", function() {
|
|
if (typeof w !== "undefined") {
|
|
return w;
|
|
} else {
|
|
return "auto";
|
|
}
|
|
});
|
|
image.css("height", function() {
|
|
if (typeof h !== "undefined") {
|
|
return h;
|
|
} else {
|
|
return "auto";
|
|
}
|
|
});
|
|
if (typeof c !== "undefined") {
|
|
var classes = c.split(',');
|
|
for (i = 0; i < classes.length; i++) {
|
|
image.addClass(classes[i]);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// for the window resize
|
|
$(window).resize(function() {
|
|
setMenuHeight();
|
|
});
|
|
// for the sticky header
|
|
$(window).scroll(function() {
|
|
// add shadow when not in top position
|
|
if ($(this).scrollTop() == 0) {
|
|
$('#top-bar').removeClass("is-sticky");
|
|
}
|
|
else {
|
|
$('#top-bar').addClass("is-sticky");
|
|
}
|
|
});
|
|
// debouncing function from John Hann
|
|
// http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
|
|
(function($, sr) {
|
|
|
|
var debounce = function(func, threshold, execAsap) {
|
|
var timeout;
|
|
|
|
return function debounced() {
|
|
var obj = this, args = arguments;
|
|
|
|
function delayed() {
|
|
if (!execAsap)
|
|
func.apply(obj, args);
|
|
timeout = null;
|
|
};
|
|
|
|
if (timeout)
|
|
clearTimeout(timeout);
|
|
else if (execAsap)
|
|
func.apply(obj, args);
|
|
|
|
timeout = setTimeout(delayed, threshold || 100);
|
|
};
|
|
}
|
|
// smartresize
|
|
jQuery.fn[sr] = function(fn) { return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr); };
|
|
|
|
})(jQuery, 'smartresize');
|
|
|
|
jQuery(function() {
|
|
restoreTabSelections();
|
|
initMermaid();
|
|
scrollToActiveMenu();
|
|
|
|
jQuery('#sidebar .category-icon').on('click', function() {
|
|
$( this ).toggleClass("fa-angle-down fa-angle-right") ;
|
|
$( this ).parent().parent().children('ul').toggle() ;
|
|
return false;
|
|
});
|
|
|
|
var sidebarStatus = searchStatus = 'open';
|
|
$('#sidebar .highlightable').perfectScrollbar();
|
|
setMenuHeight();
|
|
|
|
jQuery('#overlay').on('click', function() {
|
|
jQuery(document.body).toggleClass('sidebar-hidden');
|
|
sidebarStatus = (jQuery(document.body).hasClass('sidebar-hidden') ? 'closed' : 'open');
|
|
|
|
return false;
|
|
});
|
|
|
|
jQuery('[data-sidebar-toggle]').on('click', function() {
|
|
jQuery(document.body).toggleClass('sidebar-hidden');
|
|
sidebarStatus = (jQuery(document.body).hasClass('sidebar-hidden') ? 'closed' : 'open');
|
|
|
|
return false;
|
|
});
|
|
jQuery('[data-clear-history-toggle]').on('click', function() {
|
|
sessionStorage.clear();
|
|
location.reload();
|
|
return false;
|
|
});
|
|
jQuery('[data-search-toggle]').on('click', function() {
|
|
if (sidebarStatus == 'closed') {
|
|
jQuery('[data-sidebar-toggle]').trigger('click');
|
|
jQuery(document.body).removeClass('searchbox-hidden');
|
|
searchStatus = 'open';
|
|
|
|
return false;
|
|
}
|
|
|
|
jQuery(document.body).toggleClass('searchbox-hidden');
|
|
searchStatus = (jQuery(document.body).hasClass('searchbox-hidden') ? 'closed' : 'open');
|
|
|
|
return false;
|
|
});
|
|
|
|
var ajax;
|
|
jQuery('[data-search-input]').on('input', function() {
|
|
var input = jQuery(this),
|
|
value = input.val(),
|
|
items = jQuery('[data-nav-id]');
|
|
items.removeClass('search-match');
|
|
if (!value.length) {
|
|
$('ul.topics').removeClass('searched');
|
|
items.css('display', 'block');
|
|
sessionStorage.removeItem('search-value');
|
|
$("mark").parents(".expand-marked").removeClass("expand-marked");
|
|
$(".highlightable").unhighlight({ element: 'mark' })
|
|
return;
|
|
}
|
|
|
|
sessionStorage.setItem('search-value', value);
|
|
$("mark").parents(".expand-marked").removeClass("expand-marked");
|
|
$(".highlightable").unhighlight({ element: 'mark' }).highlight(value, { element: 'mark' });
|
|
$("mark").parents(".expand").addClass("expand-marked");
|
|
|
|
if (ajax && ajax.abort) ajax.abort();
|
|
|
|
jQuery('[data-search-clear]').on('click', function() {
|
|
jQuery('[data-search-input]').val('').trigger('input');
|
|
sessionStorage.removeItem('search-input');
|
|
$("mark").parents(".expand-marked").removeClass("expand-marked");
|
|
$(".highlightable").unhighlight({ element: 'mark' })
|
|
});
|
|
});
|
|
|
|
$.expr[":"].contains = $.expr.createPseudo(function(arg) {
|
|
return function( elem ) {
|
|
return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
|
|
};
|
|
});
|
|
|
|
if (sessionStorage.getItem('search-value')) {
|
|
var searchValue = sessionStorage.getItem('search-value')
|
|
$(document.body).removeClass('searchbox-hidden');
|
|
$('[data-search-input]').val(searchValue);
|
|
$('[data-search-input]').trigger('input');
|
|
var searchedElem = $('#body-inner').find(':contains(' + searchValue + ')').get(0);
|
|
if (searchedElem) {
|
|
searchedElem.scrollIntoView(true);
|
|
var scrolledY = window.scrollY;
|
|
if(scrolledY){
|
|
window.scroll(0, scrolledY - 125);
|
|
}
|
|
}
|
|
}
|
|
|
|
$(".highlightable").highlight(sessionStorage.getItem('search-value'), { element: 'mark' });
|
|
$("mark").parents(".expand").addClass("expand-marked");
|
|
|
|
// clipboard
|
|
var clipInit = false;
|
|
$('code').each(function() {
|
|
var code = $(this),
|
|
text = code.text();
|
|
|
|
if (text.length > 5) {
|
|
if (!clipInit) {
|
|
var text, clip = new ClipboardJS('.copy-to-clipboard', {
|
|
text: function(trigger) {
|
|
text = $(trigger).prev('code').text();
|
|
return text.replace(/^\$\s/gm, '');
|
|
}
|
|
});
|
|
|
|
var inPre;
|
|
clip.on('success', function(e) {
|
|
e.clearSelection();
|
|
inPre = $(e.trigger).parent().prop('tagName') == 'PRE';
|
|
$(e.trigger).attr('aria-label', 'Copied to clipboard!').addClass('tooltipped tooltipped-' + (inPre ? 'w' : 's'));
|
|
});
|
|
|
|
clip.on('error', function(e) {
|
|
inPre = $(e.trigger).parent().prop('tagName') == 'PRE';
|
|
$(e.trigger).attr('aria-label', fallbackMessage(e.action)).addClass('tooltipped tooltipped-' + (inPre ? 'w' : 's'));
|
|
$(document).one('copy', function(){
|
|
$(e.trigger).attr('aria-label', 'Copied to clipboard!').addClass('tooltipped tooltipped-' + (inPre ? 'w' : 's'));
|
|
});
|
|
});
|
|
|
|
clipInit = true;
|
|
}
|
|
|
|
code.addClass('copy-to-clipboard-inline');
|
|
code.after('<span class="copy-to-clipboard" title="Copy to clipboard" />');
|
|
code.next('.copy-to-clipboard').on('mouseleave', function() {
|
|
$(this).attr('aria-label', null).removeClass('tooltipped tooltipped-s tooltipped-w');
|
|
});
|
|
}
|
|
});
|
|
|
|
// allow keyboard control for prev/next links
|
|
jQuery(function() {
|
|
jQuery('.nav-prev').click(function(){
|
|
location.href = jQuery(this).attr('href');
|
|
});
|
|
jQuery('.nav-next').click(function() {
|
|
location.href = jQuery(this).attr('href');
|
|
});
|
|
});
|
|
|
|
jQuery('input, textarea').keydown(function (e) {
|
|
// left and right arrow keys
|
|
if (e.which == '37' || e.which == '39') {
|
|
e.stopPropagation();
|
|
}
|
|
});
|
|
|
|
jQuery(document).keydown(function(e) {
|
|
// prev links - left arrow key
|
|
if(e.which == '37') {
|
|
jQuery('.nav.nav-prev').click();
|
|
}
|
|
|
|
// next links - right arrow key
|
|
if(e.which == '39') {
|
|
jQuery('.nav.nav-next').click();
|
|
}
|
|
});
|
|
|
|
$('#top-bar a:not(:has(img)):not(.btn)').addClass('highlight');
|
|
$('#body-inner a:not(:has(img)):not(.btn):not(a[rel="footnote"])').addClass('highlight');
|
|
|
|
var touchsupport = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)
|
|
if (!touchsupport){ // browser doesn't support touch
|
|
$('#toc-menu').hover(function() {
|
|
$('.progress').stop(true, false, true).fadeToggle(100);
|
|
});
|
|
|
|
$('.progress').hover(function() {
|
|
$('.progress').stop(true, false, true).fadeToggle(100);
|
|
});
|
|
}
|
|
if (touchsupport){ // browser does support touch
|
|
$('#toc-menu').click(function() {
|
|
$('.progress').stop(true, false, true).fadeToggle(100);
|
|
});
|
|
$('.progress').click(function() {
|
|
$('.progress').stop(true, false, true).fadeToggle(100);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fix anchor scrolling that hides behind top nav bar
|
|
* Courtesy of https://stackoverflow.com/a/13067009/28106
|
|
*
|
|
* We could use pure css for this if only heading anchors were
|
|
* involved, but this works for any anchor, including footnotes
|
|
**/
|
|
(function (document, history, location) {
|
|
var HISTORY_SUPPORT = !!(history && history.pushState);
|
|
|
|
var anchorScrolls = {
|
|
ANCHOR_REGEX: /^#[^ ]+$/,
|
|
OFFSET_HEIGHT_PX: 50,
|
|
|
|
/**
|
|
* Establish events, and fix initial scroll position if a hash is provided.
|
|
*/
|
|
init: function () {
|
|
this.scrollToCurrent();
|
|
$(window).on('hashchange', $.proxy(this, 'scrollToCurrent'));
|
|
$('body').on('click', 'a', $.proxy(this, 'delegateAnchors'));
|
|
},
|
|
|
|
/**
|
|
* Return the offset amount to deduct from the normal scroll position.
|
|
* Modify as appropriate to allow for dynamic calculations
|
|
*/
|
|
getFixedOffset: function () {
|
|
return this.OFFSET_HEIGHT_PX;
|
|
},
|
|
|
|
/**
|
|
* If the provided href is an anchor which resolves to an element on the
|
|
* page, scroll to it.
|
|
* @param {String} href
|
|
* @return {Boolean} - Was the href an anchor.
|
|
*/
|
|
scrollIfAnchor: function (href, pushToHistory) {
|
|
var match, anchorOffset;
|
|
|
|
if (!this.ANCHOR_REGEX.test(href)) {
|
|
return false;
|
|
}
|
|
|
|
match = document.getElementById(href.slice(1));
|
|
|
|
if (match) {
|
|
anchorOffset = $(match).offset().top - this.getFixedOffset();
|
|
$('html, body').animate({ scrollTop: anchorOffset });
|
|
|
|
// Add the state to history as-per normal anchor links
|
|
if (HISTORY_SUPPORT && pushToHistory) {
|
|
history.pushState({}, document.title, location.pathname + href);
|
|
}
|
|
}
|
|
|
|
return !!match;
|
|
},
|
|
|
|
/**
|
|
* Attempt to scroll to the current location's hash.
|
|
*/
|
|
scrollToCurrent: function (e) {
|
|
if (this.scrollIfAnchor(window.location.hash) && e) {
|
|
e.preventDefault();
|
|
}
|
|
},
|
|
|
|
/**
|
|
* If the click event's target was an anchor, fix the scroll position.
|
|
*/
|
|
delegateAnchors: function (e) {
|
|
var elem = e.target;
|
|
|
|
if (this.scrollIfAnchor(elem.getAttribute('href'), true)) {
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
};
|
|
|
|
$(document).ready($.proxy(anchorScrolls, 'init'));
|
|
})(window.document, window.history, window.location);
|
|
|
|
// Add link button for every
|
|
var text, clip = new ClipboardJS('.anchor');
|
|
$("h1~h2,h1~h3,h1~h4,h1~h5,h1~h6").append(function(index, html){
|
|
var element = $(this);
|
|
var url = encodeURI(document.location.origin + document.location.pathname);
|
|
var link = url + "#"+element[0].id;
|
|
var html = " " + $( "<span>" ).addClass("anchor").attr("data-clipboard-text", link).append("<i class='fas fa-link fa-lg'></i>").get(0).outerHTML;
|
|
return html;
|
|
});
|
|
|
|
$(".anchor").on('mouseleave', function(e) {
|
|
$(this).attr('aria-label', null).removeClass('tooltipped tooltipped-s tooltipped-w');
|
|
});
|
|
|
|
clip.on('success', function(e) {
|
|
e.clearSelection();
|
|
$(e.trigger).attr('aria-label', 'Link copied to clipboard!').addClass('tooltipped tooltipped-s');
|
|
});
|
|
|
|
$('a[rel="lightbox"]').featherlight({
|
|
root: 'div#body'
|
|
});
|
|
|
|
sessionStorage.setItem(jQuery('body').data('url'), 1);
|
|
|
|
// loop through the sessionStorage and see if something should be marked as visited
|
|
for (var url in sessionStorage) {
|
|
if (sessionStorage.getItem(url) == 1){
|
|
// in case we have `relativeURLs=true` we have to strip the
|
|
// relative path to root
|
|
url = url.replace( /\.\.\//g, '/' ).replace( /^\/+\//, '/' );
|
|
jQuery('[data-nav-id="' + url + '"]').addClass('visited');
|
|
}
|
|
}
|
|
});
|
|
|
|
jQuery.extend({
|
|
highlight: function(node, re, nodeName, className) {
|
|
if (node.nodeType === 3 && node.parentElement && node.parentElement.namespaceURI == 'http://www.w3.org/1999/xhtml') { // text nodes
|
|
var match = node.data.match(re);
|
|
if (match) {
|
|
var highlight = document.createElement(nodeName || 'span');
|
|
highlight.className = className || 'highlight';
|
|
var wordNode = node.splitText(match.index);
|
|
wordNode.splitText(match[0].length);
|
|
var wordClone = wordNode.cloneNode(true);
|
|
highlight.appendChild(wordClone);
|
|
wordNode.parentNode.replaceChild(highlight, wordNode);
|
|
return 1; //skip added node in parent
|
|
}
|
|
} else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
|
|
!/(script|style)/i.test(node.tagName) && // ignore script and style nodes
|
|
!(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted
|
|
for (var i = 0; i < node.childNodes.length; i++) {
|
|
i += jQuery.highlight(node.childNodes[i], re, nodeName, className);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
});
|
|
|
|
jQuery.fn.unhighlight = function(options) {
|
|
var settings = {
|
|
className: 'highlight',
|
|
element: 'span'
|
|
};
|
|
jQuery.extend(settings, options);
|
|
|
|
return this.find(settings.element + "." + settings.className).each(function() {
|
|
var parent = this.parentNode;
|
|
parent.replaceChild(this.firstChild, this);
|
|
parent.normalize();
|
|
}).end();
|
|
};
|
|
|
|
jQuery.fn.highlight = function(words, options) {
|
|
var settings = {
|
|
className: 'highlight',
|
|
element: 'span',
|
|
caseSensitive: false,
|
|
wordsOnly: false
|
|
};
|
|
jQuery.extend(settings, options);
|
|
|
|
if (!words) { return; }
|
|
|
|
if (words.constructor === String) {
|
|
words = [words];
|
|
}
|
|
words = jQuery.grep(words, function(word, i) {
|
|
return word != '';
|
|
});
|
|
words = jQuery.map(words, function(word, i) {
|
|
return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
});
|
|
if (words.length == 0) { return this; }
|
|
;
|
|
|
|
var flag = settings.caseSensitive ? "" : "i";
|
|
var pattern = "(" + words.join("|") + ")";
|
|
if (settings.wordsOnly) {
|
|
pattern = "\\b" + pattern + "\\b";
|
|
}
|
|
var re = new RegExp(pattern, flag);
|
|
|
|
return this.each(function() {
|
|
jQuery.highlight(this, re, settings.element, settings.className);
|
|
});
|
|
};
|