// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information. | |
$(function () { | |
var active = 'active'; | |
var expanded = 'in'; | |
var collapsed = 'collapsed'; | |
var filtered = 'filtered'; | |
var show = 'show'; | |
var hide = 'hide'; | |
var util = new utility(); | |
highlight(); | |
enableSearch(); | |
renderTables(); | |
renderAlerts(); | |
renderLinks(); | |
renderNavbar(); | |
renderSidebar(); | |
renderAffix(); | |
renderFooter(); | |
renderLogo(); | |
breakText(); | |
window.refresh = function (article) { | |
// Update markup result | |
if (typeof article == 'undefined' || typeof article.content == 'undefined') | |
console.error("Null Argument"); | |
$("article.content").html(article.content); | |
highlight(); | |
renderTables(); | |
renderAlerts(); | |
renderAffix(); | |
} | |
function breakText() { | |
$(".xref").addClass("text-break"); | |
var texts = $(".text-break"); | |
texts.each(function () { | |
$(this).text(function (index, text) { | |
return util.breakText(text); | |
}) | |
}); | |
} | |
// Styling for tables in conceptual documents using Bootstrap. | |
// See http://getbootstrap.com/css/#tables | |
function renderTables() { | |
$('table').addClass('table table-bordered table-striped table-condensed'); | |
} | |
// Styling for alerts. | |
function renderAlerts() { | |
$('.NOTE, .TIP').addClass('alert alert-info'); | |
$('.WARNING').addClass('alert alert-warning'); | |
$('.IMPORTANT, .CAUTION').addClass('alert alert-danger'); | |
} | |
// Enable anchors for headings. | |
(function () { | |
anchors.options = { | |
placement: 'left', | |
visible: 'touch' | |
}; | |
anchors.add('article h2:not(.no-anchor), article h3:not(.no-anchor), article h4:not(.no-anchor)'); | |
})(); | |
// Open links to different host in a new window. | |
function renderLinks() { | |
if ($("meta[property='docfx:newtab']").attr("content") === "true") { | |
$(document.links).filter(function () { | |
return this.hostname !== window.location.hostname; | |
}).attr('target', '_blank'); | |
} | |
} | |
// Enable highlight.js | |
function highlight() { | |
$('pre code').each(function (i, block) { | |
hljs.highlightBlock(block); | |
}); | |
$('pre code[highlight-lines]').each(function (i, block) { | |
if (block.innerHTML === "") return; | |
var lines = block.innerHTML.split('\n'); | |
queryString = block.getAttribute('highlight-lines'); | |
if (!queryString) return; | |
var ranges = queryString.split(','); | |
for (var j = 0, range; range = ranges[j++];) { | |
var found = range.match(/^(\d+)\-(\d+)?$/); | |
if (found) { | |
// consider region as `{startlinenumber}-{endlinenumber}`, in which {endlinenumber} is optional | |
var start = +found[1]; | |
var end = +found[2]; | |
if (isNaN(end) || end > lines.length) { | |
end = lines.length; | |
} | |
} else { | |
// consider region as a sigine line number | |
if (isNaN(range)) continue; | |
var start = +range; | |
var end = start; | |
} | |
if (start <= 0 || end <= 0 || start > end || start > lines.length) { | |
// skip current region if invalid | |
continue; | |
} | |
lines[start - 1] = '<span class="line-highlight">' + lines[start - 1]; | |
lines[end - 1] = lines[end - 1] + '</span>'; | |
} | |
block.innerHTML = lines.join('\n'); | |
}); | |
} | |
// Support full-text-search | |
function enableSearch() { | |
var query; | |
var relHref = $("meta[property='docfx\\:rel']").attr("content"); | |
if (typeof relHref === 'undefined') { | |
return; | |
} | |
try { | |
var worker = new Worker(relHref + 'styles/search-worker.js'); | |
if (!worker && !window.worker) { | |
localSearch(); | |
} else { | |
webWorkerSearch(); | |
} | |
renderSearchBox(); | |
highlightKeywords(); | |
addSearchEvent(); | |
} catch (e) { | |
console.error(e); | |
} | |
//Adjust the position of search box in navbar | |
function renderSearchBox() { | |
autoCollapse(); | |
$(window).on('resize', autoCollapse); | |
$(document).on('click', '.navbar-collapse.in', function (e) { | |
if ($(e.target).is('a')) { | |
$(this).collapse('hide'); | |
} | |
}); | |
function autoCollapse() { | |
var navbar = $('#autocollapse'); | |
if (navbar.height() === null) { | |
setTimeout(autoCollapse, 300); | |
} | |
navbar.removeClass(collapsed); | |
if (navbar.height() > 60) { | |
navbar.addClass(collapsed); | |
} | |
} | |
} | |
// Search factory | |
function localSearch() { | |
console.log("using local search"); | |
var lunrIndex = lunr(function () { | |
this.ref('href'); | |
this.field('title', { boost: 50 }); | |
this.field('keywords', { boost: 20 }); | |
}); | |
lunr.tokenizer.seperator = /[\s\-\.]+/; | |
var searchData = {}; | |
var searchDataRequest = new XMLHttpRequest(); | |
var indexPath = relHref + "index.json"; | |
if (indexPath) { | |
searchDataRequest.open('GET', indexPath); | |
searchDataRequest.onload = function () { | |
if (this.status != 200) { | |
return; | |
} | |
searchData = JSON.parse(this.responseText); | |
for (var prop in searchData) { | |
if (searchData.hasOwnProperty(prop)){ | |
lunrIndex.add(searchData[prop]); | |
} | |
} | |
} | |
searchDataRequest.send(); | |
} | |
$("body").bind("queryReady", function () { | |
var hits = lunrIndex.search(query); | |
var results = []; | |
hits.forEach(function (hit) { | |
var item = searchData[hit.ref]; | |
results.push({ 'href': item.href, 'title': item.title, 'keywords': item.keywords }); | |
}); | |
handleSearchResults(results); | |
}); | |
} | |
function webWorkerSearch() { | |
console.log("using Web Worker"); | |
var indexReady = $.Deferred(); | |
worker.onmessage = function (oEvent) { | |
switch (oEvent.data.e) { | |
case 'index-ready': | |
indexReady.resolve(); | |
break; | |
case 'query-ready': | |
var hits = oEvent.data.d; | |
handleSearchResults(hits); | |
break; | |
} | |
} | |
indexReady.promise().done(function () { | |
$("body").bind("queryReady", function () { | |
worker.postMessage({ q: query }); | |
}); | |
}); | |
} | |
// Highlight the searching keywords | |
function highlightKeywords() { | |
var q = url('?q'); | |
if (q !== null) { | |
var keywords = q.split("%20"); | |
keywords.forEach(function (keyword) { | |
if (keyword !== "") { | |
$('.data-searchable *').mark(keyword); | |
$('article *').mark(keyword); | |
} | |
}); | |
} | |
} | |
function addSearchEvent() { | |
$('body').bind("searchEvent", function () { | |
$('#search-query').keypress(function (e) { | |
return e.which !== 13; | |
}); | |
$('#search-query').keyup(function () { | |
query = $(this).val(); | |
if (query.length < 3) { | |
flipContents("show"); | |
} else { | |
flipContents("hide"); | |
$("body").trigger("queryReady"); | |
$('#search-results>.search-list').text('Search Results for "' + query + '"'); | |
} | |
}).off("keydown"); | |
}); | |
} | |
function flipContents(action) { | |
if (action === "show") { | |
$('.hide-when-search').show(); | |
$('#search-results').hide(); | |
} else { | |
$('.hide-when-search').hide(); | |
$('#search-results').show(); | |
} | |
} | |
function relativeUrlToAbsoluteUrl(currentUrl, relativeUrl) { | |
var currentItems = currentUrl.split(/\/+/); | |
var relativeItems = relativeUrl.split(/\/+/); | |
var depth = currentItems.length - 1; | |
var items = []; | |
for (var i = 0; i < relativeItems.length; i++) { | |
if (relativeItems[i] === '..') { | |
depth--; | |
} else if (relativeItems[i] !== '.') { | |
items.push(relativeItems[i]); | |
} | |
} | |
return currentItems.slice(0, depth).concat(items).join('/'); | |
} | |
function extractContentBrief(content) { | |
var briefOffset = 512; | |
var words = query.split(/\s+/g); | |
var queryIndex = content.indexOf(words[0]); | |
var briefContent; | |
if (queryIndex > briefOffset) { | |
return "..." + content.slice(queryIndex - briefOffset, queryIndex + briefOffset) + "..."; | |
} else if (queryIndex <= briefOffset) { | |
return content.slice(0, queryIndex + briefOffset) + "..."; | |
} | |
} | |
function handleSearchResults(hits) { | |
var numPerPage = 10; | |
$('#pagination').empty(); | |
$('#pagination').removeData("twbs-pagination"); | |
if (hits.length === 0) { | |
$('#search-results>.sr-items').html('<p>No results found</p>'); | |
} else { | |
$('#pagination').twbsPagination({ | |
totalPages: Math.ceil(hits.length / numPerPage), | |
visiblePages: 5, | |
onPageClick: function (event, page) { | |
var start = (page - 1) * numPerPage; | |
var curHits = hits.slice(start, start + numPerPage); | |
$('#search-results>.sr-items').empty().append( | |
curHits.map(function (hit) { | |
var currentUrl = window.location.href; | |
var itemRawHref = relativeUrlToAbsoluteUrl(currentUrl, relHref + hit.href); | |
var itemHref = relHref + hit.href + "?q=" + query; | |
var itemTitle = hit.title; | |
var itemBrief = extractContentBrief(hit.keywords); | |
var itemNode = $('<div>').attr('class', 'sr-item'); | |
var itemTitleNode = $('<div>').attr('class', 'item-title').append($('<a>').attr('href', itemHref).attr("target", "_blank").text(itemTitle)); | |
var itemHrefNode = $('<div>').attr('class', 'item-href').text(itemRawHref); | |
var itemBriefNode = $('<div>').attr('class', 'item-brief').text(itemBrief); | |
itemNode.append(itemTitleNode).append(itemHrefNode).append(itemBriefNode); | |
return itemNode; | |
}) | |
); | |
query.split(/\s+/).forEach(function (word) { | |
if (word !== '') { | |
$('#search-results>.sr-items *').mark(word); | |
} | |
}); | |
} | |
}); | |
} | |
} | |
}; | |
// Update href in navbar | |
function renderNavbar() { | |
var navbar = $('#navbar ul')[0]; | |
if (typeof (navbar) === 'undefined') { | |
loadNavbar(); | |
} else { | |
$('#navbar ul a.active').parents('li').addClass(active); | |
renderBreadcrumb(); | |
} | |
function loadNavbar() { | |
var navbarPath = $("meta[property='docfx\\:navrel']").attr("content"); | |
if (!navbarPath) { | |
return; | |
} | |
navbarPath = navbarPath.replace(/\\/g, '/'); | |
var tocPath = $("meta[property='docfx\\:tocrel']").attr("content") || ''; | |
if (tocPath) tocPath = tocPath.replace(/\\/g, '/'); | |
$.get(navbarPath, function (data) { | |
$(data).find("#toc>ul").appendTo("#navbar"); | |
if ($('#search-results').length !== 0) { | |
$('#search').show(); | |
$('body').trigger("searchEvent"); | |
} | |
var index = navbarPath.lastIndexOf('/'); | |
var navrel = ''; | |
if (index > -1) { | |
navrel = navbarPath.substr(0, index + 1); | |
} | |
$('#navbar>ul').addClass('navbar-nav'); | |
var currentAbsPath = util.getAbsolutePath(window.location.pathname); | |
// set active item | |
$('#navbar').find('a[href]').each(function (i, e) { | |
var href = $(e).attr("href"); | |
if (util.isRelativePath(href)) { | |
href = navrel + href; | |
$(e).attr("href", href); | |
// TODO: currently only support one level navbar | |
var isActive = false; | |
var originalHref = e.name; | |
if (originalHref) { | |
originalHref = navrel + originalHref; | |
if (util.getDirectory(util.getAbsolutePath(originalHref)) === util.getDirectory(util.getAbsolutePath(tocPath))) { | |
isActive = true; | |
} | |
} else { | |
if (util.getAbsolutePath(href) === currentAbsPath) { | |
isActive = true; | |
} | |
} | |
if (isActive) { | |
$(e).addClass(active); | |
} | |
} | |
}); | |
renderNavbar(); | |
}); | |
} | |
} | |
function renderSidebar() { | |
var sidetoc = $('#sidetoggle .sidetoc')[0]; | |
if (typeof (sidetoc) === 'undefined') { | |
loadToc(); | |
} else { | |
registerTocEvents(); | |
if ($('footer').is(':visible')) { | |
$('.sidetoc').addClass('shiftup'); | |
} | |
// Scroll to active item | |
var top = 0; | |
$('#toc a.active').parents('li').each(function (i, e) { | |
$(e).addClass(active).addClass(expanded); | |
$(e).children('a').addClass(active); | |
top += $(e).position().top; | |
}) | |
$('.sidetoc').scrollTop(top - 50); | |
if ($('footer').is(':visible')) { | |
$('.sidetoc').addClass('shiftup'); | |
} | |
renderBreadcrumb(); | |
} | |
function registerTocEvents() { | |
$('.toc .nav > li > .expand-stub').click(function (e) { | |
$(e.target).parent().toggleClass(expanded); | |
}); | |
$('.toc .nav > li > .expand-stub + a:not([href])').click(function (e) { | |
$(e.target).parent().toggleClass(expanded); | |
}); | |
$('#toc_filter_input').on('input', function (e) { | |
var val = this.value; | |
if (val === '') { | |
// Clear 'filtered' class | |
$('#toc li').removeClass(filtered).removeClass(hide); | |
return; | |
} | |
// Get leaf nodes | |
$('#toc li>a').filter(function (i, e) { | |
return $(e).siblings().length === 0 | |
}).each(function (i, anchor) { | |
var text = $(anchor).attr('title'); | |
var parent = $(anchor).parent(); | |
var parentNodes = parent.parents('ul>li'); | |
for (var i = 0; i < parentNodes.length; i++) { | |
var parentText = $(parentNodes[i]).children('a').attr('title'); | |
if (parentText) text = parentText + '.' + text; | |
}; | |
if (filterNavItem(text, val)) { | |
parent.addClass(show); | |
parent.removeClass(hide); | |
} else { | |
parent.addClass(hide); | |
parent.removeClass(show); | |
} | |
}); | |
$('#toc li>a').filter(function (i, e) { | |
return $(e).siblings().length > 0 | |
}).each(function (i, anchor) { | |
var parent = $(anchor).parent(); | |
if (parent.find('li.show').length > 0) { | |
parent.addClass(show); | |
parent.addClass(filtered); | |
parent.removeClass(hide); | |
} else { | |
parent.addClass(hide); | |
parent.removeClass(show); | |
parent.removeClass(filtered); | |
} | |
}) | |
function filterNavItem(name, text) { | |
if (!text) return true; | |
if (name.toLowerCase().indexOf(text.toLowerCase()) > -1) return true; | |
return false; | |
} | |
}); | |
} | |
function loadToc() { | |
var tocPath = $("meta[property='docfx\\:tocrel']").attr("content"); | |
if (!tocPath) { | |
return; | |
} | |
tocPath = tocPath.replace(/\\/g, '/'); | |
$('#sidetoc').load(tocPath + " #sidetoggle > div", function () { | |
var index = tocPath.lastIndexOf('/'); | |
var tocrel = ''; | |
if (index > -1) { | |
tocrel = tocPath.substr(0, index + 1); | |
} | |
var currentHref = util.getAbsolutePath(window.location.pathname); | |
$('#sidetoc').find('a[href]').each(function (i, e) { | |
var href = $(e).attr("href"); | |
if (util.isRelativePath(href)) { | |
href = tocrel + href; | |
$(e).attr("href", href); | |
} | |
if (util.getAbsolutePath(e.href) === currentHref) { | |
$(e).addClass(active); | |
} | |
$(e).text(function (index, text) { | |
return util.breakText(text); | |
}) | |
}); | |
renderSidebar(); | |
}); | |
} | |
} | |
function renderBreadcrumb() { | |
var breadcrumb = []; | |
$('#navbar a.active').each(function (i, e) { | |
breadcrumb.push({ | |
href: e.href, | |
name: e.innerHTML | |
}); | |
}) | |
$('#toc a.active').each(function (i, e) { | |
breadcrumb.push({ | |
href: e.href, | |
name: e.innerHTML | |
}); | |
}) | |
var html = util.formList(breadcrumb, 'breadcrumb'); | |
$('#breadcrumb').html(html); | |
} | |
//Setup Affix | |
function renderAffix() { | |
var hierarchy = getHierarchy(); | |
if (hierarchy.length > 0) { | |
var html = '<h5 class="title">In This Article</h5>' | |
html += util.formList(hierarchy, ['nav', 'bs-docs-sidenav']); | |
$("#affix").empty().append(html); | |
if ($('footer').is(':visible')) { | |
$(".sideaffix").css("bottom", "70px"); | |
} | |
$('#affix').on('activate.bs.scrollspy', function (e) { | |
if (e.target) { | |
if ($(e.target).find('li.active').length > 0) { | |
return; | |
} | |
var top = $(e.target).position().top; | |
$(e.target).parents('li').each(function (i, e) { | |
top += $(e).position().top; | |
}); | |
var container = $('#affix > ul'); | |
var height = container.height(); | |
container.scrollTop(container.scrollTop() + top - height / 2); | |
} | |
}) | |
} | |
function getHierarchy() { | |
// supported headers are h1, h2, h3, and h4 | |
// The topest header is ignored | |
var selector = ".article article"; | |
var affixSelector = "#affix"; | |
var headers = ['h4', 'h3', 'h2', 'h1']; | |
var hierarchy = []; | |
var toppestIndex = -1; | |
var startIndex = -1; | |
// 1. get header hierarchy | |
for (var i = headers.length - 1; i >= 0; i--) { | |
var header = $(selector + " " + headers[i]); | |
var length = header.length; | |
// If contains no header in current selector, find the next one | |
if (length === 0) continue; | |
// If the toppest header contains only one item, e.g. title, ignore | |
if (length === 1 && hierarchy.length === 0 && toppestIndex < 0) { | |
toppestIndex = i; | |
continue; | |
} | |
// Get second level children | |
var nextLevelSelector = i > 0 ? headers[i - 1] : null; | |
var prevSelector; | |
for (var j = length - 1; j >= 0; j--) { | |
var e = header[j]; | |
var id = e.id; | |
if (!id) continue; // For affix, id is a must-have | |
var item = { | |
name: htmlEncode($(e).text()), | |
href: "#" + id, | |
items: [] | |
}; | |
if (nextLevelSelector) { | |
var selector = '#' + cssEscape(id) + "~" + nextLevelSelector; | |
var currentSelector = selector; | |
if (prevSelector) currentSelector += ":not(" + prevSelector + ")"; | |
$(header[j]).siblings(currentSelector).each(function (index, e) { | |
if (e.id) { | |
item.items.push({ | |
name: htmlEncode($(e).text()), // innerText decodes text while innerHTML not | |
href: "#" + e.id | |
}) | |
} | |
}) | |
prevSelector = selector; | |
} | |
hierarchy.push(item); | |
} | |
break; | |
}; | |
hierarchy.reverse(); | |
return hierarchy; | |
} | |
function htmlEncode(str) { | |
if (!str) return str; | |
return str | |
.replace(/&/g, '&') | |
.replace(/"/g, '"') | |
.replace(/'/g, ''') | |
.replace(/</g, '<') | |
.replace(/>/g, '>'); | |
} | |
function htmlDecode(value) { | |
if (!str) return str; | |
return value | |
.replace(/"/g, '"') | |
.replace(/'/g, "'") | |
.replace(/</g, '<') | |
.replace(/>/g, '>') | |
.replace(/&/g, '&'); | |
} | |
function cssEscape(str) { | |
// see: http://stackoverflow.com/questions/2786538/need-to-escape-a-special-character-in-a-jquery-selector-string#answer-2837646 | |
if (!str) return str; | |
return str | |
.replace(/[!"#$%&'()*+,.\/:;<=>?@[\\\]^`{|}~]/g, "\\$&"); | |
} | |
} | |
// Show footer | |
function renderFooter() { | |
initFooter(); | |
$(window).on("scroll", showFooterCore); | |
function initFooter() { | |
if (needFooter()) { | |
shiftUpBottomCss(); | |
$("footer").show(); | |
} else { | |
resetBottomCss(); | |
$("footer").hide(); | |
} | |
} | |
function showFooterCore() { | |
if (needFooter()) { | |
shiftUpBottomCss(); | |
$("footer").fadeIn(); | |
} else { | |
resetBottomCss(); | |
$("footer").fadeOut(); | |
} | |
} | |
function needFooter() { | |
var scrollHeight = $(document).height(); | |
var scrollPosition = $(window).height() + $(window).scrollTop(); | |
return (scrollHeight - scrollPosition) < 1; | |
} | |
function resetBottomCss() { | |
$(".sidetoc").removeClass("shiftup"); | |
$(".sideaffix").removeClass("shiftup"); | |
} | |
function shiftUpBottomCss() { | |
$(".sidetoc").addClass("shiftup"); | |
$(".sideaffix").addClass("shiftup"); | |
} | |
} | |
function renderLogo() { | |
// For LOGO SVG | |
// Replace SVG with inline SVG | |
// http://stackoverflow.com/questions/11978995/how-to-change-color-of-svg-image-using-css-jquery-svg-image-replacement | |
jQuery('img.svg').each(function () { | |
var $img = jQuery(this); | |
var imgID = $img.attr('id'); | |
var imgClass = $img.attr('class'); | |
var imgURL = $img.attr('src'); | |
jQuery.get(imgURL, function (data) { | |
// Get the SVG tag, ignore the rest | |
var $svg = jQuery(data).find('svg'); | |
// Add replaced image's ID to the new SVG | |
if (typeof imgID !== 'undefined') { | |
$svg = $svg.attr('id', imgID); | |
} | |
// Add replaced image's classes to the new SVG | |
if (typeof imgClass !== 'undefined') { | |
$svg = $svg.attr('class', imgClass + ' replaced-svg'); | |
} | |
// Remove any invalid XML tags as per http://validator.w3.org | |
$svg = $svg.removeAttr('xmlns:a'); | |
// Replace image with new SVG | |
$img.replaceWith($svg); | |
}, 'xml'); | |
}); | |
} | |
function utility() { | |
this.getAbsolutePath = getAbsolutePath; | |
this.isRelativePath = isRelativePath; | |
this.isAbsolutePath = isAbsolutePath; | |
this.getDirectory = getDirectory; | |
this.formList = formList; | |
this.breakText = breakText; | |
function getAbsolutePath(href) { | |
// Use anchor to normalize href | |
var anchor = $('<a href="' + href + '"></a>')[0]; | |
// Ignore protocal, remove search and query | |
return anchor.host + anchor.pathname; | |
} | |
function isRelativePath(href) { | |
return !isAbsolutePath(href); | |
} | |
function isAbsolutePath(href) { | |
return (/^(?:[a-z]+:)?\/\//i).test(href); | |
} | |
function getDirectory(href) { | |
if (!href) return ''; | |
var index = href.lastIndexOf('/'); | |
if (index == -1) return ''; | |
if (index > -1) { | |
return href.substr(0, index); | |
} | |
} | |
function formList(item, classes) { | |
var level = 1; | |
var model = { | |
items: item | |
}; | |
var cls = [].concat(classes).join(" "); | |
return getList(model, cls); | |
function getList(model, cls) { | |
if (!model || !model.items) return null; | |
var l = model.items.length; | |
if (l === 0) return null; | |
var html = '<ul class="level' + level + ' ' + (cls || '') + '">'; | |
level++; | |
for (var i = 0; i < l; i++) { | |
var item = model.items[i]; | |
var href = item.href; | |
var name = item.name; | |
if (!name) continue; | |
html += href ? '<li><a href="' + href + '">' + name + '</a>' : '<li>' + name; | |
html += getList(item, cls) || ''; | |
html += '</li>'; | |
} | |
html += '</ul>'; | |
return html; | |
} | |
} | |
function breakText(text) { | |
if (!text) return text; | |
return text.replace(/([a-z])([A-Z])|(\.)(\w)/g, '$1$3\u200B$2$4') | |
} | |
} | |
}) |