JavaScript cleanup (#3253)

- Upgrade from UglifyJS to Babel as a JS preprocessor. This lets me use modern ES6 syntax
in the JS code.

- Update JS code to leverage ES6.

- Remove most dependencies on JQuery for faster & smaller code. Once I remove the
dependency on the Bootstrap library, then the dependency on JQuery will also completely
go away.
This commit is contained in:
Martin Taillefer 2019-02-13 10:10:28 -08:00 committed by GitHub
parent f1f79e6fbe
commit 3eba576a89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 384 additions and 404 deletions

View File

@ -2,7 +2,7 @@ version: 2
jobs:
build:
docker:
- image: gcr.io/istio-testing/website-builder:2019-02-12
- image: gcr.io/istio-testing/website-builder:2019-02-13
working_directory: ~/site

View File

@ -1,4 +1,4 @@
img := gcr.io/istio-testing/website-builder:2019-02-12
img := gcr.io/istio-testing/website-builder:2019-02-13
docker := docker run -e INTERNAL_ONLY=true -t -i --sig-proxy=true --rm -v $(shell pwd):/site -w /site $(img)
ifeq ($(INTERNAL_ONLY),)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1,3 @@
"use strict";function applyStyleSheet(e){var t,i;for(t=0;i=document.getElementsByTagName("link")[t];t++)-1!==i.getAttribute("rel").indexOf("style")&&i.getAttribute("title")&&(i.disabled=!0,i.getAttribute("title")===e&&(i.disabled=!1));var l=document.getElementById("light-theme-item");null!==l&&("light"===e?l.classList.add("active"):l.classList.remove("active")),null!==(l=document.getElementById("dark-theme-item"))&&("dark"===e?l.classList.add("active"):l.classList.remove("active"))}function getPreferredStyleSheet(){var e,t;for(e=0;t=document.getElementsByTagName("link")[e];e++)if(-1!==t.getAttribute("rel").indexOf("style")&&-1===t.getAttribute("rel").indexOf("alt")&&t.getAttribute("title"))return t.getAttribute("title");return null}function createCookie(e,t,i){if(i){var l=new Date;l.setTime(l.getTime()+24*i*60*60*1e3);var r="; expires="+l.toGMTString()}else r="";document.cookie=e+"="+t+r+"; path=/"}function readCookie(e){for(var t=e+"=",i=document.cookie.split(";"),l=0;l<i.length;l++){for(var r=i[l];" "===r.charAt(0);)r=r.substring(1,r.length);if(0===r.indexOf(t))return r.substring(t.length,r.length)}return null}function setActiveStyleSheet(e){applyStyleSheet(e),createCookie("style",e)}function loadActiveStyleSheet(){var e=readCookie("style");applyStyleSheet(null===e?getPreferredStyleSheet():e)}window.onload=function(e){loadActiveStyleSheet()},loadActiveStyleSheet();
"use strict";function applyStyleSheet(b){let c,d;for(c=0;d=document.getElementsByTagName("link")[c];c++)-1!==d.getAttribute("rel").indexOf("style")&&d.getAttribute("title")&&(d.disabled=d.getAttribute("title")!==b);let e=document.getElementById("light-theme-item");null!==e&&("light"===b?e.classList.add("active"):e.classList.remove("active")),e=document.getElementById("dark-theme-item"),null!==e&&("dark"===b?e.classList.add("active"):e.classList.remove("active"))}function getPreferredStyleSheet(){let b,c;for(b=0;c=document.getElementsByTagName("link")[b];b++)if(-1!==c.getAttribute("rel").indexOf("style")&&-1===c.getAttribute("rel").indexOf("alt")&&c.getAttribute("title"))return c.getAttribute("title");return null}function createCookie(a,b,c){let d="";if(c){const a=new Date;a.setTime(a.getTime()+1e3*(60*(60*(24*c)))),d="; expires="+a.toGMTString()}document.cookie=a+"="+b+d+"; path=/"}function readCookie(a){const b=a+"=",d=document.cookie.split(";");for(let e,c=0;c<d.length;c++){for(e=d[c];" "===e.charAt(0);)e=e.substring(1,e.length);if(0===e.indexOf(b))return e.substring(b.length,e.length)}return null}function setActiveStyleSheet(a){applyStyleSheet(a),createCookie("style",a)}function loadActiveStyleSheet(){let a=readCookie("style");null===a?applyStyleSheet(getPreferredStyleSheet()):applyStyleSheet(a)}window.onload=function(){loadActiveStyleSheet()},loadActiveStyleSheet();
//# sourceMappingURL=styleSwitcher.min.js.map

View File

@ -1 +1 @@
{"version":3,"sources":["src/js/styleSwitcher.js"],"names":["applyStyleSheet","title","i","a","document","getElementsByTagName","getAttribute","indexOf","disabled","item","getElementById","classList","add","remove","getPreferredStyleSheet","createCookie","name","value","days","date","Date","setTime","getTime","expires","toGMTString","cookie","readCookie","nameEQ","ca","split","length","c","charAt","substring","setActiveStyleSheet","loadActiveStyleSheet","window","onload","e"],"mappings":"AAAA,aAEA,SAASA,gBAAgBC,GACrB,IAAIC,EAAGC,EACP,IAAKD,EAAI,EAAIC,EAAIC,SAASC,qBAAqB,QAAQH,GAAKA,KACR,IAA5CC,EAAEG,aAAa,OAAOC,QAAQ,UAAmBJ,EAAEG,aAAa,WAChEH,EAAEK,UAAW,EACTL,EAAEG,aAAa,WAAaL,IAC5BE,EAAEK,UAAW,IAOzB,IAAIC,EAAOL,SAASM,eAAe,oBACtB,OAATD,IACc,UAAVR,EACAQ,EAAKE,UAAUC,IAAI,UAEnBH,EAAKE,UAAUE,OAAO,WAKjB,QADbJ,EAAOL,SAASM,eAAe,sBAEb,SAAVT,EACAQ,EAAKE,UAAUC,IAAI,UAEnBH,EAAKE,UAAUE,OAAO,WAKlC,SAASC,yBACL,IAAIZ,EAAGC,EACP,IAAKD,EAAI,EAAIC,EAAIC,SAASC,qBAAqB,QAAQH,GAAKA,IACxD,IAAgD,IAA5CC,EAAEG,aAAa,OAAOC,QAAQ,WACe,IAA1CJ,EAAEG,aAAa,OAAOC,QAAQ,QAC9BJ,EAAEG,aAAa,SAClB,OAAOH,EAAEG,aAAa,SAG9B,OAAO,KAGX,SAASS,aAAaC,EAAMC,EAAOC,GAC/B,GAAIA,EAAM,CACN,IAAIC,EAAO,IAAIC,KACfD,EAAKE,QAAQF,EAAKG,UAAoB,GAAPJ,EAAY,GAAK,GAAK,KACrD,IAAIK,EAAU,aAAeJ,EAAKK,mBAElCD,EAAU,GAEdnB,SAASqB,OAAST,EAAO,IAAMC,EAAQM,EAAU,WAGrD,SAASG,WAAWV,GAGhB,IAFA,IAAIW,EAASX,EAAO,IAChBY,EAAKxB,SAASqB,OAAOI,MAAM,KACtB3B,EAAI,EAAGA,EAAI0B,EAAGE,OAAQ5B,IAAK,CAEhC,IADA,IAAI6B,EAAIH,EAAG1B,GACY,MAAhB6B,EAAEC,OAAO,IACZD,EAAIA,EAAEE,UAAU,EAAGF,EAAED,QAGzB,GAA0B,IAAtBC,EAAExB,QAAQoB,GACV,OAAOI,EAAEE,UAAUN,EAAOG,OAAQC,EAAED,QAG5C,OAAO,KAGX,SAASI,oBAAoBjC,GACzBD,gBAAgBC,GAChBc,aAAa,QAASd,GAG1B,SAASkC,uBACL,IAAIV,EAASC,WAAW,SAEpB1B,gBADW,OAAXyB,EACgBX,yBAEAW,GAIxBW,OAAOC,OAAS,SAAUC,GACtBH,wBAGJA"}
{"version":3,"sources":["../../src/js/styleSwitcher.js"],"names":[],"mappings":"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","file":"styleSwitcher.min.js","sourcesContent":["\"use strict\";\r\rfunction applyStyleSheet(title) {\r let i, a;\r for (i = 0; (a = document.getElementsByTagName(\"link\")[i]); i++) {\r if (a.getAttribute(\"rel\").indexOf(\"style\") !== -1 && a.getAttribute(\"title\")) {\r a.disabled = a.getAttribute(\"title\") !== title;\r }\r }\r\r // set the active theme menu item\r\r let item = document.getElementById(\"light-theme-item\");\r if (item !== null) {\r if (title === \"light\") {\r item.classList.add(\"active\");\r } else {\r item.classList.remove(\"active\");\r }\r }\r\r item = document.getElementById(\"dark-theme-item\");\r if (item !== null) {\r if (title === \"dark\") {\r item.classList.add(\"active\");\r } else {\r item.classList.remove(\"active\");\r }\r }\r}\r\rfunction getPreferredStyleSheet() {\r let i, a;\r for (i = 0; (a = document.getElementsByTagName(\"link\")[i]); i++) {\r if (a.getAttribute(\"rel\").indexOf(\"style\") !== -1\r && a.getAttribute(\"rel\").indexOf(\"alt\") === -1\r && a.getAttribute(\"title\")) {\r return a.getAttribute(\"title\");\r }\r }\r return null;\r}\r\rfunction createCookie(name, value, days) {\r let expires = \"\";\r if (days) {\r const date = new Date();\r date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));\r expires = \"; expires=\" + date.toGMTString();\r }\r document.cookie = name + \"=\" + value + expires + \"; path=/\";\r}\r\rfunction readCookie(name) {\r const nameEQ = name + \"=\";\r const ca = document.cookie.split(';');\r for (let i = 0; i < ca.length; i++) {\r let c = ca[i];\r while (c.charAt(0) === ' ') {\r c = c.substring(1, c.length);\r }\r\r if (c.indexOf(nameEQ) === 0) {\r return c.substring(nameEQ.length, c.length);\r }\r }\r return null;\r}\r\rfunction setActiveStyleSheet(title) {\r applyStyleSheet(title);\r createCookie(\"style\", title);\r}\r\rfunction loadActiveStyleSheet() {\r let cookie = readCookie(\"style\");\r if (cookie === null) {\r applyStyleSheet(getPreferredStyleSheet());\r } else {\r applyStyleSheet(cookie);\r }\r}\r\rwindow.onload = function (e) {\r loadActiveStyleSheet();\r};\r\rloadActiveStyleSheet();\r"]}

View File

@ -3,7 +3,7 @@
{{ $current := .current }}
{{ $collapse := .collapse }}
<ul class="tree{{ if $collapse }} collapse{{ end }}">
<ul style="{{ if $collapse }}display:none{{ end }}">
{{ range $pages }}
{{ if eq .Parent $parent }}
{{ if not .IsPage }}

View File

@ -29,11 +29,11 @@ RUN tar -xf /tmp/hugo_${HUGO_VERSION}_Linux-64bit.tar.gz -C /tmp \
RUN npm install -g \
sass \
uglify-js \
markdown-spellcheck \
svgstore-cli \
@babel/cli \
@babel/core
RUN npm install babel-preset-minify --save-dev
RUN gem install mdl
RUN gem install html-proofer -v 3.9.2

View File

@ -2,6 +2,7 @@
HUB=gcr.io/istio-testing
VERSION=$(date +%Y-%m-%d)
VERSION=2019-02-13
docker build --no-cache -t $HUB/website-builder:$VERSION .
docker push $HUB/website-builder:$VERSION

View File

@ -11,6 +11,6 @@ npx sass src/sass/dark_theme_normal.scss dark_theme_normal.css -s compressed
npx sass src/sass/dark_theme_preliminary.scss dark_theme_preliminary.css -s compressed
mv light_theme* generated/css
mv dark_theme* generated/css
npx uglifyjs src/js/misc.js src/js/utils.js src/js/prism.js --mangle --compress -o generated/js/all.min.js --source-map
npx uglifyjs src/js/styleSwitcher.js --mangle --compress -o generated/js/styleSwitcher.min.js --source-map
npx babel src/js/misc.js src/js/prism.js src/js/utils.js --out-file generated/js/all.min.js --source-maps --minified --no-comments --presets minify
npx babel src/js/styleSwitcher.js --out-file generated/js/styleSwitcher.min.js --source-maps --minified --no-comments --presets minify
npx svgstore -o generated/img/icons.svg src/icons/**/*.svg

View File

@ -1,158 +1,9 @@
"use strict";
$(function ($) {
// Show the navbar links, hide the search box
function showNavBarLinks() {
var $form = $('#search_form');
var $textbox = $('#search_textbox');
var $links = $('#navbar-links');
$form.removeClass('active');
$links.addClass('active');
$textbox.val('');
}
// Show the navbar search box, hide the links
function showSearchBox() {
var $form = $('#search_form');
var $textbox = $('#search_textbox');
var $links = $('#navbar-links');
$form.addClass('active');
$links.removeClass('active');
$textbox.focus();
}
// Hide the search box when the user hits the ESC key
$('body').on('keyup', function(event) {
if (event.which === 27) {
showNavBarLinks();
}
});
// Show the search box
$('#search_show').on('click', function(event) {
event.preventDefault();
showSearchBox();
});
// Hide the search box
$('#search_close').on('click', function(event) {
event.preventDefault();
showNavBarLinks();
});
// When the user submits the search form, initiate a search
$('#search_form').submit(function(event) {
event.preventDefault();
var $textbox = $('#search_textbox');
var $search_page_url = $('#search_page_url');
var url = $search_page_url.val() + '?q=' + $textbox.val();
showNavBarLinks();
window.location.assign(url);
});
var recurse = false;
// Save a cookie when a user selects a tab in a tabset
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
if (recurse) {
// prevent endless recursion...
return;
}
var tab = e.target;
var cookie_name = tab.getAttribute("data-cookie-name");
var cookie_value = tab.getAttribute("data-cookie-value");
if (cookie_name === null || cookie_name === "") {
return;
}
createCookie(cookie_name, cookie_value);
var tabs = document.querySelectorAll('a[data-toggle="tab"]');
for (var i = 0; i < tabs.length; i++) {
var tab = tabs[i];
if (cookie_name === tab.getAttribute("data-cookie-name")) {
if (cookie_value === tab.getAttribute("data-cookie-value")) {
// there's gotta be a way to call the tab() function directly since I already have the
// requisite object in hand. Alas, I can't figure it out. So query the document to find
// the same object again, and call the tab function on the result.
recurse = true;
$('.nav-tabs a[href="' + tab.hash + '"]').tab('show');
recurse = false;
}
}
}
});
$(document).ready(function() {
// toggle sidebar on/off
$('#sidebar-toggler').on('click', function () {
$('#sidebar-container').toggleClass('active');
$(this).children('svg.icon').toggleClass('flipped');
});
// toggle category tree in sidebar
$(document).on('click', '.tree-toggle', function () {
$(this).children('i.chevron').toggleClass('show');
$(this).parent().children('ul.tree').toggle(200);
});
// toggle toolbar buttons
$(document).on('mouseenter', 'pre', function () {
$(this).next().addClass("toolbar-show");
$(this).next().next().addClass("toolbar-show");
$(this).next().next().next().addClass("toolbar-show");
});
// toggle toolbar buttons
$(document).on('mouseleave', 'pre', function () {
$(this).next().removeClass("toolbar-show");
$(this).next().next().removeClass("toolbar-show");
$(this).next().next().next().removeClass("toolbar-show");
});
// toggle copy button
$(document).on('mouseenter', 'button.copy', function () {
$(this).addClass("toolbar-show");
});
// toggle copy button
$(document).on('mouseleave', 'button.copy', function () {
$(this).removeClass("toolbar-show");
});
// toggle download button
$(document).on('mouseenter', 'button.download', function () {
$(this).addClass("toolbar-show");
});
// toggle download button
$(document).on('mouseleave', 'button.download', function () {
$(this).removeClass("toolbar-show");
});
// toggle print button
$(document).on('mouseenter', 'button.print', function () {
$(this).addClass("toolbar-show");
});
// toggle print button
$(document).on('mouseleave', 'button.print', function () {
$(this).removeClass("toolbar-show");
});
// activate the popovers
$("[data-toggle=popover]").popover();
});
}(jQuery));
// initialized after the DOM has been loaded by getDOMTopology
var scrollToTopButton;
var tocLinks;
var tocHeadings;
let scrollToTopButton;
let tocLinks;
let tocHeadings;
// post-processing we do once the DOM has loaded
function handleDOMLoaded() {
@ -163,33 +14,130 @@ function handleDOMLoaded() {
// way.
function patchDOM() {
// Add a toolbar to all PRE blocks
function attachToolbarToPreBlocks() {
var pre = document.getElementsByTagName('PRE');
for (var i = 0; i < pre.length; i++) {
var copyButton = document.createElement("BUTTON");
function attachLink(node) {
const anchor = document.createElement("a");
anchor.className = "header-link";
anchor.href = "#" + node.id;
anchor.setAttribute("aria-hidden", "true");
anchor.innerHTML = "<svg class='icon'><use xlink:href='" + iconFile + "#links'/></svg>";
node.appendChild(anchor);
}
// Add a link icon next to each header so people can easily get bookmarks to headers
function attachLinksToHeaders() {
for (let level = 2; level <= 6; level++) {
document.querySelectorAll("h" + level.toString()).forEach(hdr => {
if (hdr.id !== "") {
attachLink(hdr);
}
});
}
}
// Add a link icon next to each defined term so people can easily get bookmarks to them in the glossary
function attachLinksToDefinedTerms() {
document.querySelectorAll('dt').forEach(dt => {
if (dt.id !== "") {
attachLink(dt);
}
});
}
// Make it so each link outside of the current domain opens up in a different window
function makeOutsideLinksOpenInTabs() {
document.querySelectorAll('a').forEach(link => {
if (link.hostname && link.hostname !== location.hostname) {
link.setAttribute("target", "_blank");
link.setAttribute("rel", "noopener");
}
});
}
function createEndnotes() {
const notes = document.getElementById("endnotes");
if (notes === null) {
return;
}
// look for anchors in the main section of the doc only (skip headers, footers, tocs, nav bars, etc)
const main = document.getElementsByTagName("main")[0];
const map = new Map(null);
let num_links = 0;
main.querySelectorAll('a').forEach(link => {
if (link.pathname === location.pathname) {
// skip links on the current page
return;
}
if (link.pathname.endsWith("/") && link.hash !== "") {
// skip links on the current page
return;
}
if (link.classList.contains("btn")) {
// skip button links
return;
}
if (link.classList.contains("not-for-endnotes")) {
// skip links that don't want to be included
return;
}
let count = map.get(link.href);
if (count === undefined) {
count = map.size + 1;
map.set(link.href, count);
// add a list entry for the link
const li = document.createElement("li");
li.innerText = link.href;
notes.appendChild(li);
}
// add the superscript reference
link.insertAdjacentHTML("afterend", "<sup class='endnote-ref'>" + count + "</sup>");
num_links++;
});
if (num_links === 0) {
// if there are no links on this page, hide the whole section
const div = document.getElementsByClassName("link-endnotes")[0];
div.style.display = "none";
}
}
function fixupPreBlocks() {
// Add a toolbar to all PRE blocks
function attachToolbar(pre) {
const copyButton = document.createElement("BUTTON");
copyButton.title = buttonCopy;
copyButton.className = "copy";
copyButton.innerHTML = "<svg><use xlink:href='" + iconFile + "#copy'/></svg>";
copyButton.setAttribute("aria-label", "Copy to clipboard");
copyButton.addEventListener("mouseenter", (e) => e.currentTarget.classList.add("toolbar-show"));
copyButton.addEventListener("mouseleave", (e) => e.currentTarget.classList.remove("toolbar-show"));
var downloadButton = document.createElement("BUTTON");
const downloadButton = document.createElement("BUTTON");
downloadButton.title = buttonDownload;
downloadButton.className = "download";
downloadButton.innerHTML = "<svg><use xlink:href='" + iconFile + "#download'/></svg>";
downloadButton.setAttribute("aria-label", downloadButton.title);
downloadButton.onclick = function(e) {
var div = e.currentTarget.parentElement;
var codes = div.getElementsByTagName("CODE");
if ((codes !== null) && (codes.length > 0)) {
var code = codes[0];
var text = getToolbarDivText(div);
var downloadas = code.getAttribute("data-downloadas");
if (downloadas === null || downloadas === "") {
downloadas = "foo";
downloadButton.addEventListener("mouseenter", (e) => e.currentTarget.classList.add("toolbar-show"));
downloadButton.addEventListener("mouseleave", (e) => e.currentTarget.classList.remove("toolbar-show"));
var lang = "";
for (var j = 0; j < code.classList.length; j++) {
downloadButton.addEventListener("click", (e) => {
const div = e.currentTarget.parentElement;
const codes = div.getElementsByTagName("CODE");
if ((codes !== null) && (codes.length > 0)) {
const code = codes[0];
const text = getToolbarDivText(div);
let downloadas = code.getAttribute("data-downloadas");
if (downloadas === null || downloadas === "") {
let lang = "";
for (let j = 0; j < code.classList.length; j++) {
if (code.classList.item(j).startsWith("language-")) {
lang = code.classList.item(j).substr(9);
break;
@ -198,6 +146,8 @@ function handleDOMLoaded() {
if (lang.startsWith("command")) {
lang = "bash";
} else if (lang === "markdown") {
lang = "md";
} else if (lang === "") {
lang = "txt";
}
@ -207,71 +157,73 @@ function handleDOMLoaded() {
saveFile(downloadas, text);
}
return true;
};
});
var printButton = document.createElement("BUTTON");
const printButton = document.createElement("BUTTON");
printButton.title = buttonPrint;
printButton.className = "print";
printButton.innerHTML = "<svg><use xlink:href='" + iconFile + "#printer'/></svg>";
printButton.setAttribute("aria-label", printButton.title);
printButton.onclick = function(e) {
var div = e.currentTarget.parentElement;
var text = getToolbarDivText(div);
printButton.addEventListener("mouseenter", (e) => e.currentTarget.classList.add("toolbar-show"));
printButton.addEventListener("mouseleave", (e) => e.currentTarget.classList.remove("toolbar-show"));
printButton.addEventListener("click", (e) => {
const div = e.currentTarget.parentElement;
const text = getToolbarDivText(div);
printText(text);
return true;
};
});
// wrap the PRE block in a DIV so we have a place to attach the toolbar buttons
var div = document.createElement("DIV");
const div = document.createElement("DIV");
div.className = "toolbar";
pre[i].parentElement.insertBefore(div, pre[i]);
div.appendChild(pre[i]);
pre.parentElement.insertBefore(div, pre);
div.appendChild(pre);
div.appendChild(printButton);
div.appendChild(downloadButton);
div.appendChild(copyButton);
pre.addEventListener("mouseenter", (e) => {
e.currentTarget.nextSibling.classList.add("toolbar-show");
e.currentTarget.nextSibling.nextSibling.classList.add("toolbar-show");
e.currentTarget.nextSibling.nextSibling.nextSibling.classList.add("toolbar-show");
});
pre.addEventListener("mouseleave", (e) => {
e.currentTarget.nextSibling.classList.remove("toolbar-show");
e.currentTarget.nextSibling.nextSibling.classList.remove("toolbar-show");
e.currentTarget.nextSibling.nextSibling.nextSibling.classList.remove("toolbar-show");
});
}
var copyCode = new ClipboardJS('button.copy', {
text: function (trigger) {
return getToolbarDivText(trigger.parentElement);
}
});
function getToolbarDivText(div) {
const commands = div.getElementsByClassName("command");
if ((commands !== null) && (commands.length > 0)) {
const lines = commands[0].innerText.split("\n");
let cmd = "";
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith("$ ")) {
lines[i] = lines[i].substring(2);
}
copyCode.on('error', function (event) {
alert("Sorry, but copying is not supported by your browser");
});
}
if (cmd !== "") {
cmd = cmd + "\n";
}
function getToolbarDivText(div) {
var commands = div.getElementsByClassName("command");
if ((commands !== null) && (commands.length > 0)) {
var lines = commands[0].innerText.split("\n");
var cmd = "";
for (var i = 0; i < lines.length; i++) {
if (lines[i].startsWith("$ ")) {
lines[i] = lines[i].substring(2);
cmd += lines[i];
}
if (cmd !== "") {
cmd = cmd + "\n";
}
cmd += lines[i];
return cmd;
}
return cmd;
return div.innerText;
}
return div.innerText;
}
function applySyntaxColoring(pre) {
const code = pre.firstChild;
function applySyntaxColoringToPreBlocks() {
var pre = document.getElementsByTagName('PRE');
for (var i = 0; i < pre.length; i++) {
var code = pre[i].firstChild;
var cl = "";
for (var j = 0; j < code.classList.length; j++) {
let cl = "";
for (let j = 0; j < code.classList.length; j++) {
if (code.classList.item(j).startsWith("language-command")) {
cl = code.classList.item(j);
break;
@ -279,14 +231,14 @@ function handleDOMLoaded() {
}
if (cl !== "") {
var firstLineOfOutput = 0;
var lines = code.innerText.split("\n");
var cmd = "";
var escape = false;
var escapeUntilEOF = false;
var tmp = "";
for (var j = 0; j < lines.length; j++) {
var line = lines[j];
let firstLineOfOutput = 0;
let lines = code.innerText.split("\n");
let cmd = "";
let escape = false;
let escapeUntilEOF = false;
let tmp = "";
for (let j = 0; j < lines.length; j++) {
const line = lines[j];
if (line.startsWith("$ ")) {
if (tmp !== "") {
@ -325,11 +277,11 @@ function handleDOMLoaded() {
if (cmd !== "") {
cmd = cmd.replace(/@(.*?)@/g, "<a href='https://raw.githubusercontent.com/istio/istio/" + branchName + "/$1'>$1</a>");
var html = "<div class='command'>" + cmd + "</div>";
let html = "<div class='command'>" + cmd + "</div>";
var output = "";
let output = "";
if (firstLineOfOutput > 0) {
for (var j = firstLineOfOutput; j < lines.length; j++) {
for (let j = firstLineOfOutput; j < lines.length; j++) {
if (output !== "") {
output += "\n";
}
@ -339,9 +291,9 @@ function handleDOMLoaded() {
if (output !== "") {
// apply formatting to the output?
var prefix = "language-command-output-as-";
let prefix = "language-command-output-as-";
if (cl.startsWith(prefix)) {
var lang = cl.substr(prefix.length);
let lang = cl.substr(prefix.length);
output = Prism.highlight(output, Prism.languages[lang], lang);
} else {
output = escapeHTML(output);
@ -362,178 +314,193 @@ function handleDOMLoaded() {
Prism.highlightElement(code, false);
}
}
// Load the content of any externally-hosted PRE block
function loadExternal(pre) {
function fetchFile(elem, url) {
fetch(url).then(function (response) {
return response.text();
}).then(function (data) {
elem.firstChild.textContent = data;
Prism.highlightElement(elem.firstChild, false);
});
}
if (pre.hasAttribute("data-src")) {
fetchFile(pre, pre.getAttribute("data-src"))
}
}
document.querySelectorAll('pre').forEach((pre) => {
attachToolbar(pre);
applySyntaxColoring(pre);
loadExternal(pre);
});
const clipboard = new ClipboardJS('button.copy', {
text: function (trigger) {
return getToolbarDivText(trigger.parentElement);
}
});
clipboard.on('error', () => alert("Sorry, but copying is not supported by your browser"));
}
function attachLink(node) {
var anchor = document.createElement("a");
anchor.className = "header-link";
anchor.href = "#" + node.id;
anchor.setAttribute("aria-hidden", "true");
anchor.innerHTML = "<svg class='icon'><use xlink:href='" + iconFile + "#links'/></svg>";
node.appendChild(anchor);
}
// Add a link icon next to each header so people can easily get bookmarks to headers
function attachLinksToHeaders() {
for (var level = 2; level <= 6; level++) {
var headers = document.getElementsByTagName("h" + level.toString());
for (var i = 0; i < headers.length; i++) {
var header = headers[i];
if (header.id !== "") {
attachLink(header);
}
}
}
}
// Add a link icon next to each defined term so people can easily get bookmarks to them in the glossary
function attachLinksToDefinedTerms() {
var terms = document.getElementsByTagName("dt");
for (var i = 0; i < terms.length; i++) {
var term = terms[i];
if (term.id !== "") {
attachLink(term);
}
}
}
// Make it so each link outside of the current domain opens up in a different window
function makeOutsideLinksOpenInTabs() {
var links = document.getElementsByTagName("a");
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.hostname && link.hostname !== location.hostname) {
link.setAttribute("target", "_blank");
link.setAttribute("rel", "noopener");
}
}
}
// Load the content of any externally-hosted PRE blocks
function loadExternalPreBlocks() {
function fetchFile(elem, url) {
fetch(url).then(function (response) {
return response.text();
}).then(function (data) {
elem.firstChild.textContent = data;
Prism.highlightElement(elem.firstChild, false);
});
}
var pre = document.getElementsByTagName('PRE');
for (var i = 0; i < pre.length; i++) {
if (pre[i].hasAttribute("data-src")) {
fetchFile(pre[i], pre[i].getAttribute("data-src"))
}
}
}
function createEndnotes() {
var notes = document.getElementById("endnotes");
if (notes === null) {
return;
}
// look for anchors in the main section of the doc only (skip headers, footers, tocs, nav bars, etc)
var main = document.getElementsByTagName("main")[0];
var links = main.getElementsByTagName("a");
var map = new Map(null);
var num_links = 0;
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.pathname === location.pathname) {
// skip links on the current page
continue;
}
if (link.pathname.endsWith("/") && link.hash !== "") {
// skip links on the current page
continue;
}
if (link.classList.contains("btn")) {
// skip button links
continue;
}
if (link.classList.contains("not-for-endnotes")) {
// skip links that don't want to be included
continue;
}
var count = map.get(link.href);
if (count === undefined) {
count = map.size + 1;
map.set(link.href, count);
// add a list entry for the link
var li = document.createElement("li");
li.innerText = link.href;
notes.appendChild(li);
}
// add the superscript reference
link.insertAdjacentHTML("afterend", "<sup class='endnote-ref'>" + count + "</sup>");
num_links++;
}
if (num_links === 0) {
// if there are no links on this page, hide the whole section
var div = document.getElementsByClassName("link-endnotes")[0];
div.style.display = "none";
}
}
attachToolbarToPreBlocks();
applySyntaxColoringToPreBlocks();
fixupPreBlocks();
attachLinksToHeaders();
attachLinksToDefinedTerms();
makeOutsideLinksOpenInTabs();
loadExternalPreBlocks();
createEndnotes();
}
function selectTabs() {
var tabs = document.querySelectorAll('a[data-toggle="tab"]');
for (var i = 0; i < tabs.length; i++) {
var tab = tabs[i];
var cookie_name = tab.getAttribute("data-cookie-name");
var cookie_value = tab.getAttribute("data-cookie-value");
document.querySelectorAll('a[data-toggle="tab"]').forEach(tab => {
const cookie_name = tab.getAttribute("data-cookie-name");
const cookie_value = tab.getAttribute("data-cookie-value");
if (cookie_name === null || cookie_name === "") {
continue;
return;
}
var v = readCookie(cookie_name);
const v = readCookie(cookie_name);
if (cookie_value === v) {
// there's gotta be a way to call the tab() function directly since I already have the
// requisite object in hand. Alas, I can't figure it out. So query the document to find
// the same object again, and call the tab function on the result.
$('.nav-tabs a[href="' + tab.hash + '"]').tab('show');
}
}
});
}
// discover a few DOM elements up front so we don't need to do it a zillion times for the life of the page
function getDOMTopology() {
scrollToTopButton = document.getElementById("scroll-to-top");
var toc = document.getElementById("toc");
const toc = document.getElementById("toc");
if (toc !== null) {
tocLinks = toc.getElementsByTagName("A");
tocHeadings = new Array(tocLinks.length);
for (var i = 0; i < tocLinks.length; i++) {
for (let i = 0; i < tocLinks.length; i++) {
tocHeadings[i] = document.getElementById(tocLinks[i].hash.substring(1));
}
}
}
function attachSearchHandlers() {
// Show the navbar links, hide the search box
function showNavBarLinks() {
document.getElementById('search_form').classList.remove('active');
document.getElementById('navbar-links').classList.add('active');
document.getElementById('search_textbox').value = '';
}
// Show the navbar search box, hide the links
function showSearchBox() {
document.getElementById('search_form').classList.add('active');
document.getElementById('navbar-links').classList.remove('active');
document.getElementById('search_textbox').focus();
}
// Hide the search box when the user hits the ESC key
document.body.addEventListener("keyup", e => {
if (e.which === 27) {
showNavBarLinks();
}
});
// Show the search box
document.getElementById('search_show').addEventListener("click", e => {
e.preventDefault();
showSearchBox();
});
// Hide the search box
document.getElementById('search_close').addEventListener("click", e => {
e.preventDefault();
showNavBarLinks();
});
// When the user submits the search form, initiate a search
document.getElementById('search_form').addEventListener("submit", e => {
e.preventDefault();
const textbox = document.getElementById('search_textbox');
const search_page_url = document.getElementById('search_page_url');
const url = search_page_url.value + '?q=' + textbox.value;
showNavBarLinks();
window.location.assign(url);
});
}
function attachSidebarHandlers() {
// toggle sidebar on/off
document.getElementById('sidebar-toggler').addEventListener("click", (e) => {
document.getElementById("sidebar-container").classList.toggle('active');
e.currentTarget.querySelector('svg.icon').classList.toggle('flipped');
});
// toggle subtree in sidebar
document.querySelectorAll('.tree-toggle').forEach(o => {
o.addEventListener("click", () => {
o.querySelectorAll('i.chevron').forEach(chevron => {
chevron.classList.toggle('show');
});
if (o.nextElementSibling.style.display === "none") {
o.nextElementSibling.style.display = "block";
} else {
o.nextElementSibling.style.display = "none";
}
});
});
}
let recurse = false;
function attachTabHandlers() {
// Save a cookie when a user selects a tab in a tabset
$('a[data-toggle="tab"]').on('shown.bs.tab', e => {
if (recurse) {
// prevent endless recursion...
return;
}
let tab = e.target;
let cookie_name = tab.getAttribute("data-cookie-name");
let cookie_value = tab.getAttribute("data-cookie-value");
if (cookie_name === null || cookie_name === "") {
return;
}
createCookie(cookie_name, cookie_value);
document.querySelectorAll('a[data-toggle="tab"]').forEach(tab => {
if (cookie_name === tab.getAttribute("data-cookie-name")) {
if (cookie_value === tab.getAttribute("data-cookie-value")) {
// there's gotta be a way to call the tab() function directly since I already have the
// DOM object in hand. Alas, I can't figure it out. So query and call the tab function on the result.
recurse = true;
$('.nav-tabs a[href="' + tab.hash + '"]').tab('show');
recurse = false;
}
}
});
});
}
function enablePopovers() {
// activate the popovers
$("[data-toggle=popover]").popover();
}
patchDOM();
selectTabs();
getDOMTopology();
attachSearchHandlers();
attachSidebarHandlers();
attachTabHandlers();
enablePopovers();
// one forced call here to make sure everything looks right
handlePageScroll();
@ -555,18 +522,18 @@ function handlePageScroll() {
// Based on the scroll position, activate a TOC entry
function controlTOCActivation() {
if (tocLinks) {
var closestHeadingBelowTop = -1;
var closestHeadingBelowTopPos = 1000000;
var closestHeadingAboveTop = -1;
var closestHeadingAboveTopPos = -1000000;
let closestHeadingBelowTop = -1;
let closestHeadingBelowTopPos = 1000000;
let closestHeadingAboveTop = -1;
let closestHeadingAboveTopPos = -1000000;
for (var i = 0; i < tocLinks.length; i++) {
var heading = tocHeadings[i];
for (let i = 0; i < tocLinks.length; i++) {
const heading = tocHeadings[i];
if (heading === null) {
continue;
}
var cbr = heading.getBoundingClientRect();
const cbr = heading.getBoundingClientRect();
if (cbr.width || cbr.height) {
if ((cbr.top >= 0) && (cbr.top < window.innerHeight)) {

View File

@ -1 +1 @@
"use strict"; function applyStyleSheet(title) { var i, a; for (i = 0; (a = document.getElementsByTagName("link")[i]); i++) { if (a.getAttribute("rel").indexOf("style") !== -1 && a.getAttribute("title")) { a.disabled = true; if (a.getAttribute("title") === title) { a.disabled = false; } } } // set the active theme menu item var i, a; if (a.getAttribute("rel").indexOf("style") !== -1 && a.getAttribute("title")) { if (item !== null) { if (title === "light") { item.classList.add("active"); } else { item.classList.remove("active"); } } item = document.getElementById("dark-theme-item"); if (item !== null) { if (title === "dark") { item.classList.add("active"); } else { item.classList.remove("active"); } } } function getPreferredStyleSheet() { var i, a; for (i = 0; (a = document.getElementsByTagName("link")[i]); i++) { if (a.getAttribute("rel").indexOf("style") !== -1 && a.getAttribute("rel").indexOf("alt") === -1 && a.getAttribute("title")) { return a.getAttribute("title"); } } return null; } function createCookie(name, value, days) { if (days) { var date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); var expires = "; expires=" + date.toGMTString(); } else { if (a.getAttribute("rel").indexOf("style") !== -1 && a.getAttribute("title")) { if (a.getAttribute("title") === title) { } document.cookie = name + "=" + value + expires + "; path=/"; } function readCookie(name) { if (a.getAttribute("rel").indexOf("style") !== -1 && a.getAttribute("title")) { } if (a.getAttribute("rel").indexOf("style") !== -1 && a.getAttribute("title")) { } a.disabled = true; a.disabled = true; var i, a; while (c.charAt(0) === ' ') { c = c.substring(1, c.length); } if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length, c.length); } } return null; } function setActiveStyleSheet(title) { applyStyleSheet(title); createCookie("style", title); } function loadActiveStyleSheet() { var cookie = readCookie("style"); if (cookie === null) { applyStyleSheet(getPreferredStyleSheet()); } else { applyStyleSheet(cookie); } } window.onload = function (e) { loadActiveStyleSheet(); }; loadActiveStyleSheet();
"use strict"; function applyStyleSheet(title) { if (a.getAttribute("title") === title) { var i, a; for (i = 0; (a = document.getElementsByTagName("link")[i]); i++) { if (a.getAttribute("rel").indexOf("style") !== -1 && a.getAttribute("title")) { if (a.getAttribute("title") === title) { for (i = 0; (a = document.getElementsByTagName("link")[i]); i++) { } } // set the active theme menu item if (a.getAttribute("title") === title) { if (a.getAttribute("rel").indexOf("style") !== -1 && a.getAttribute("title")) { if (item !== null) { if (title === "light") { item.classList.add("active"); } else { item.classList.remove("active"); } } item = document.getElementById("dark-theme-item"); if (item !== null) { if (title === "dark") { item.classList.add("active"); } else { item.classList.remove("active"); } } } function getPreferredStyleSheet() { let i, a; for (i = 0; (a = document.getElementsByTagName("link")[i]); i++) { if (a.getAttribute("rel").indexOf("style") !== -1 && a.getAttribute("rel").indexOf("alt") === -1 && a.getAttribute("title")) { return a.getAttribute("title"); } } return null; } function createCookie(name, value, days) { let expires = ""; if (days) { const date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); if (a.getAttribute("title") === title) { } } document.cookie = name + "=" + value + expires + "; path=/"; } function readCookie(name) { if (a.getAttribute("title") === title) { } if (a.getAttribute("title") === title) { } a.disabled = false; a.disabled = false; var i, a; while (c.charAt(0) === ' ') { c = c.substring(1, c.length); } if (c.indexOf(nameEQ) === 0) { return c.substring(nameEQ.length, c.length); } } return null; } function setActiveStyleSheet(title) { applyStyleSheet(title); createCookie("style", title); } function loadActiveStyleSheet() { let cookie = readCookie("style"); if (cookie === null) { applyStyleSheet(getPreferredStyleSheet()); } else { applyStyleSheet(cookie); } } window.onload = function (e) { loadActiveStyleSheet(); }; loadActiveStyleSheet();

View File

@ -6,21 +6,21 @@ function scrollToTop() {
document.documentElement.scrollTop = 0; // for Chrome, Firefox, IE and Opera
}
var escapeChars = {
'¢' : 'cent',
'£' : 'pound',
'¥' : 'yen',
'€' : 'euro',
'©' :'copy',
'®' : 'reg',
'<' : 'lt',
'>' : 'gt',
'"' : 'quot',
'&' : 'amp',
'\'' : '#39'
const escapeChars = {
'¢': 'cent',
'£': 'pound',
'¥': 'yen',
'€': 'euro',
'©': 'copy',
'®': 'reg',
'<': 'lt',
'>': 'gt',
'"': 'quot',
'&': 'amp',
'\'': '#39'
};
var regex = new RegExp("[¢£¥€©®<>\"&']", 'g');
const regex = new RegExp("[¢£¥€©®<>\"&']", 'g');
// Escapes special characters into HTML entities
function escapeHTML(str) {
@ -31,7 +31,7 @@ function escapeHTML(str) {
// Saves a string to a particular client-side file
function saveFile(filename, text) {
var element = document.createElement('a');
const element = document.createElement('a');
element.setAttribute('href', 'data:text/text;charset=utf-8,' + encodeURI(text));
element.setAttribute('download', filename);
element.click();
@ -39,9 +39,9 @@ function saveFile(filename, text) {
// Sends a string to the printer
function printText(text) {
var html="<html><body><pre><code>" + text + "</code></pre></html>";
const html = "<html><body><pre><code>" + text + "</code></pre></html>";
var printWin = window.open('','','left=0,top=0,width=100,height=100,toolbar=0,scrollbars=0,status=0,location=0,menubar=0', false);
const printWin = window.open('', '', 'left=0,top=0,width=100,height=100,toolbar=0,scrollbars=0,status=0,location=0,menubar=0', false);
printWin.document.write(html);
printWin.document.close();
printWin.focus();

View File

@ -28,6 +28,12 @@
padding-right: 1rem;
}
@media screen AND (min-width: $bp-lg) {
grid-template-columns: [sidebar] 20% [article] calc(80% - 1rem);
padding-left: 1rem;
padding-right: 1rem;
}
@media screen AND (min-width: $bp-xl) {
grid-template-columns: [sidebar] 16% [article] calc(68% - 2rem) [toc] 16%;