/*
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
https://www.gnu.org/licenses/
*/
/* Link event binding code. */
// Given a node, finds all tags and mocks them
mockNode = function (node) {
links = node.getElementsByTagName("a");
mockCollection(links);
}
// Given an HTMLCollection, mocks the localized links
mockCollection = function (collection) {
links = [];
// Turn collection into an array for easy filter/map
for (var ii = 0; ii < collection.length; ii++) {
links[ii] = collection[ii];
}
links.filter(referencesRemote).map(mockLink);
}
// Given an , binds navigateTo and preventDefault, marks link as mocked
mockLink = function (a) {
a.addEventListener("click", function (e) {
if (e.ctrlKey === true) return;
e.preventDefault();
navigateTo(a.href);
});
a.setAttribute("data-mocked", "true");
}
// Check if an node references a local path and hasn't been mocked
// mocking is the act by which we take a link and suppress it with JS
referencesRemote = function (a) {
// Use a.getAttribute instead of a.href since a.href coerces to full url
url = a.getAttribute("href");
// If the link has no href, ignore it
if (url == undefined) return false;
// If the link has mocking disabled, ignore it
if (a.getAttribute("data-disable-mocking") == "true") return false;
// If the link has already been mocked
if (a.getAttribute("data-mocked") == "true") return false;
// If the link is only a fragment, consider it local
if ((/^#/).test(url)) return false;
// If the link has a URI scheme, consider it non-local
hasScheme = (/^\w+:\/\//).test(url)
return !hasScheme;
}
/* History state changers and handlers */
navigateTo = function (url) {
url = sanitizeUrl(url);
history.pushState({}, "", url);
onNavigate();
}
onNavigate = function () {
showCurrentPage();
}
window.onpopstate = onNavigate;
// Main entry point for a url change (popstate) event
// mediates between all associated content getting and embedding
showCurrentPage = function () {
var path = window.location.pathname;
// If we are navigating to root, simply close the current page and stop.
if (path === "/") {
var profile = document.getElementById("profile");
profile.style.backgroundImage = "url(/<%=./utils/static/path.sh static/profile.jpg=%>)";
// Add small delay to allow profile to be ready to flex before content
// closes and blurb fades in
setTimeout(function () {
document.body.classList.remove("open");
setTitleFromSection();
selectSection(path);
}, 20);
return;
}
var section = findSection(path);
if (section == undefined) {
console.log("section does not exist", path)
clearSelection();
document.body.classList.add("open");
getContent(path, function (content) {
var section = createSection(path, content);
setTitleFromSection(section);
setTimeout(selectSection.bind(window, path), 100);
}, function (response) {
errorSection("Error " + response.status + " has occurred.
" + response.response);
});
} else {
var section = selectSection(path);
setTitleFromSection(section);
document.body.classList.add("open");
}
}
setTitleFromSection = function (section) {
if (section == undefined) return document.title = "Dylan Thinnes";
var newTitle = section.getElementsByTagName("title")[0];
if (newTitle == undefined) return document.title = "Dylan Thinnes";
return document.title = newTitle.innerHTML;
}
/* URL getting and replacement */
// gets the content at the endpoint without the layout
getContent = function (url, callback, error) {
var req = new XMLHttpRequest();
req.onreadystatechange = function () {
if (req.readyState == 4) {
if (req.status == 200) callback(req.response);
else error(req);
}
}
if (url[0] != "/") throw "Url is not absolute."
if (/\/\w+$/.test(url)) url += "/index.html"
else if (/\/\w+\/$/.test(url)) url += "index.html"
req.open("GET", "/nolayout" + url);
req.send();
}
/* Section Management */
// Sections contain the content for a given endpoint and have a data-url attr
// The page may be initialized with any number of sections
var sanitizeUrl = function (url) {
if (url.slice(-1) === '/') {
return url.slice(0,-1);
} else if (url.slice(-11) === '/index.html') {
return url.slice(0,-11);
} else {
return url;
}
}
// Finds the section corresponding to a given url
findSection = function (url) {
url = sanitizeUrl(url);
if (url === "ERROR") return document.querySelector(".section#error");
var sections = document.getElementsByClassName("section");
for (var ii = 0; ii < sections.length; ii++) {
var section = sections[ii];
var sectionUrl = section.getAttribute("data-url");
if (sectionUrl == undefined) continue;
sectionUrl = sanitizeUrl(sectionUrl);
if (sectionUrl == url) return section;
}
}
// create a section container for content retrieved from an endpoint
// append it to the page container
createSection = function (url, content) {
url = sanitizeUrl(url);
var section = document.createElement("div");
section.classList.add("section");
section.setAttribute("data-url", url);
var sectionContainer = document.createElement("div");
sectionContainer.classList.add("section-container")
sectionContainer.innerHTML = content;
section.appendChild(sectionContainer);
var content = document.getElementById("content");
var loading = content.querySelector("#loading");
content.insertBefore(section, loading);
mockNode(section);
initSection(section);
return section;
}
// Initializes a new section by building a toc & running all scripts.
initSection = function (section) {
// This runs after all scripts are fetched.
var rest = function () {
var toc = section.getElementsByClassName("toc")[0];
if (toc != null) {
var toggler = document.createElement("a");
toggler.className = "toggler button";
toggler.addEventListener("click", function () {
toc.classList.toggle("hidden")
})
var title = toc.getElementsByTagName("h3")[0];
title.appendChild(toggler);
}
if (window.Prism != undefined) {
var codeBlocks = section.querySelectorAll("pre > code");
for (var ii = 0; ii < codeBlocks.length; ii++) {
var codeBlock = codeBlocks[ii];
var classes = codeBlock.parentNode.classList;
for (var jj = 0; jj < classes.length; jj++) {
var currClass = classes[jj];
if (Prism.languages[currClass] != undefined) {
codeBlock.classList.add("language-" + currClass);
Prism.highlightElement(codeBlock);
break;
}
}
}
}
}
// Explicitly execute scripts
var scripts = section.getElementsByTagName("script");
var ii = 0;
var runScript = function () {
// Get current script, then progress counter to next script.
var origScript = scripts[ii];
ii++;
// If the original script is null, just run the rest
if (origScript == null) {
rest();
} else {
var newScript = document.createElement("script");
newScript.type = 'text/javascript'
if (origScript.src) {
// If remote, run as remote and run rest of scripts once this
// one is done / errors out
newScript.onload = runScript
newScript.onerror = runScript
newScript.src = origScript.src
document.head.appendChild(newScript);
} else {
// If inline, just run that code
try {
newScript.textContent = origScript.innerText
document.head.appendChild(newScript);
} catch (e) {
console.log("Error occurred with script: ", origScript);
}
runScript();
}
}
}
runScript();
}
// select a section
selectSection = function (url) {
clearSelection();
section = findSection(url);
section.classList.add("selected");
return section;
}
getSelectedSections = function () {
return document.querySelectorAll(".section.selected");
}
// unselect all sections
clearSelection = function (url) {
var sections = document.getElementsByClassName("section");
for (var ii = 0; ii < sections.length; ii++) {
sections[ii].classList.remove("selected");
}
}
// select the error section
errorSection = function (newError) {
console.log("SETTING ERROR: ", newError);
var errorText = findSection("ERROR").getElementsByClassName("text")[0];
if (errorText != null) errorText.innerHTML = newError;
selectSection("ERROR");
}
// Initialize mocking of links in page.
mockNode(document);
console.log("Nodes mocked.")
var ss = getSelectedSections();
for (var ii = 0; ii < ss.length; ii++) {
initSection(ss[ii]);
}