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.
242 lines
6.6 KiB
242 lines
6.6 KiB
(function() {
|
|
|
|
// Space optimisations
|
|
var doc = document;
|
|
var elem = doc.querySelector.bind(doc);
|
|
var create = doc.createElement.bind(doc);
|
|
|
|
// Run callback when DOM is ready
|
|
function DOMReady(fn) {
|
|
|
|
// Run now if DOM has already loaded as we're loading async
|
|
if(['interactive', 'complete'].indexOf(doc.readyState) >= 0) {
|
|
fn();
|
|
|
|
// Otherwise wait for DOM
|
|
} else {
|
|
doc.addEventListener('DOMContentLoaded', fn);
|
|
}
|
|
}
|
|
|
|
// Feature tester
|
|
function FeatureTester(tests) {
|
|
var self = this;
|
|
|
|
self.tests = tests;
|
|
|
|
self.test = function(features) {
|
|
if(!features || !features.length) {
|
|
return false;
|
|
}
|
|
return features.every(function(feature) {
|
|
return self.tests[feature];
|
|
});
|
|
}
|
|
}
|
|
|
|
// Init once, don't re-detect features each time
|
|
var supports = new FeatureTester({
|
|
localStorage: (function() {
|
|
try {
|
|
localStorage.setItem('test', 'test');
|
|
localStorage.removeItem('test');
|
|
return true;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
})(),
|
|
inlineSVG: (function() {
|
|
var div = create('div');
|
|
div.innerHTML = '<svg/>';
|
|
return (
|
|
typeof SVGRect != 'undefined'
|
|
&& div.firstChild
|
|
&& div.firstChild.namespaceURI
|
|
) == 'http://www.w3.org/2000/svg';
|
|
})(),
|
|
querySelector: typeof doc.querySelector === 'function',
|
|
classList: (function() {
|
|
var div = create('div');
|
|
div.innerHTML = '<svg/>';
|
|
return 'classList' in div.firstChild;
|
|
})(),
|
|
});
|
|
|
|
// Favourite nodes
|
|
var favouriteNodes = {
|
|
|
|
// Key used in localStorage
|
|
storageKey: 'heartedNodes',
|
|
|
|
// Url to heart SVG
|
|
heartPath: '/assets/heart.svg',
|
|
|
|
// Class added to heart SVG element when active
|
|
activeClass: 'hearted',
|
|
|
|
// Gets current node hash
|
|
getCurrentNode: function() {
|
|
var node = /^\/node\/([a-zA-Z0-9]+)/.exec(window.location.pathname);
|
|
return node ? node[1] : node;
|
|
},
|
|
|
|
// Gets current node title
|
|
getCurrentNodeTitle: function() {
|
|
return elem('h2.node-title .name').innerText;
|
|
},
|
|
|
|
// Gets hearted nodes
|
|
getHeartedNodes: function() {
|
|
return JSON.parse(localStorage.getItem(favouriteNodes.storageKey)) || {};
|
|
},
|
|
|
|
// Saves hearted nodes
|
|
saveHeartedNodes: function(heartedNodes) {
|
|
return localStorage.setItem(favouriteNodes.storageKey, JSON.stringify(heartedNodes));
|
|
},
|
|
|
|
// Checks if node is hearted
|
|
isHearted: function(node) {
|
|
return typeof favouriteNodes.getHeartedNodes()[node] !== 'undefined';
|
|
},
|
|
|
|
// Heart node
|
|
heart: function(node) {
|
|
var heartedNodes = favouriteNodes.getHeartedNodes();
|
|
heartedNodes[node] = favouriteNodes.getCurrentNodeTitle();
|
|
favouriteNodes.saveHeartedNodes(heartedNodes);
|
|
favouriteNodes.updateHeartedNodesList();
|
|
return heartedNodes;
|
|
},
|
|
|
|
// Unheart node
|
|
unHeart: function(node) {
|
|
var heartedNodes = favouriteNodes.getHeartedNodes();
|
|
delete heartedNodes[node];
|
|
favouriteNodes.saveHeartedNodes(heartedNodes);
|
|
favouriteNodes.updateHeartedNodesList();
|
|
return heartedNodes;
|
|
},
|
|
|
|
// Get list of hearted nodes
|
|
updateHeartedNodesList: function() {
|
|
var menu = elem('.menu');
|
|
if(!menu) {
|
|
return false;
|
|
}
|
|
var list = '<ul>';
|
|
var heartedNodes = favouriteNodes.getHeartedNodes();
|
|
Object.keys(heartedNodes).forEach(function(node) {
|
|
list += '<li><a href="/node/' + node + '">' + heartedNodes[node] + '</a></li>';
|
|
});
|
|
list += '</ul>';
|
|
return menu.innerHTML = list;
|
|
},
|
|
|
|
// Load SVG, run callback when loaded
|
|
loadSVG: function(cb) {
|
|
|
|
// Get heart SVG
|
|
var xhr = new XMLHttpRequest();
|
|
xhr.open('GET', favouriteNodes.heartPath);
|
|
xhr.addEventListener('load', function() {
|
|
|
|
// Create heart SVG elem
|
|
var div = create('div');
|
|
div.innerHTML = xhr.responseText;
|
|
var heartEl = div.firstChild;
|
|
|
|
// Show heart as active if we've already hearted this node
|
|
var node = favouriteNodes.getCurrentNode();
|
|
if(favouriteNodes.isHearted(node)) {
|
|
heartEl.classList.add(favouriteNodes.activeClass);
|
|
}
|
|
|
|
// Add click handler
|
|
heartEl.addEventListener('click', function() {
|
|
|
|
// Heart/unheart node
|
|
var node = favouriteNodes.getCurrentNode();
|
|
if(favouriteNodes.isHearted(node)) {
|
|
heartEl.classList.remove(favouriteNodes.activeClass);
|
|
favouriteNodes.unHeart(node);
|
|
} else {
|
|
heartEl.classList.add(favouriteNodes.activeClass);
|
|
favouriteNodes.heart(node);
|
|
}
|
|
});
|
|
|
|
// Run callback
|
|
cb(heartEl);
|
|
});
|
|
xhr.send();
|
|
},
|
|
|
|
// Initiate node favouriting
|
|
init: function() {
|
|
|
|
// Start loading heart SVG before DOM
|
|
favouriteNodes.loadSVG(function(heartEl) {
|
|
|
|
// Then inject into DOM when it's ready
|
|
DOMReady(function() {
|
|
var titleEl = elem('h2.node-title');
|
|
if(titleEl) {
|
|
titleEl.insertBefore(heartEl, titleEl.firstChild);
|
|
}
|
|
});
|
|
});
|
|
|
|
// Inject elems into DOM
|
|
DOMReady(function() {
|
|
var headerHeight = elem('.title').offsetHeight;
|
|
var headerBoxShadow = 3;
|
|
|
|
// Menu button
|
|
var menuButton = create('div');
|
|
menuButton.classList.add('menu-button');
|
|
menuButton.style.height = headerHeight + 'px';
|
|
menuButton.addEventListener('click', function() {
|
|
favouriteNodes.updateHeartedNodesList();
|
|
elem('.menu').classList.toggle('active');
|
|
});
|
|
elem('header .wrapper').appendChild(menuButton);
|
|
|
|
// Menu
|
|
var menu = create('div');
|
|
menu.classList.add('menu');
|
|
menu.style.top = (headerHeight + headerBoxShadow) + 'px';
|
|
menu.style.height = 'calc(100% - ' + (headerHeight + headerBoxShadow) + 'px)';
|
|
document.body.appendChild(menu);
|
|
favouriteNodes.updateHeartedNodesList();
|
|
});
|
|
|
|
// If current node is hearted
|
|
var node = favouriteNodes.getCurrentNode();
|
|
if(favouriteNodes.isHearted(node)) {
|
|
|
|
// Heart it again so we get the new name if it's updated
|
|
favouriteNodes.heart(node);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Init favourite nodes
|
|
if(supports.test(['localStorage', 'inlineSVG', 'querySelector', 'classList'])) {
|
|
favouriteNodes.init();
|
|
}
|
|
|
|
// Add ios class to body on iOS devices
|
|
if(supports.test(['classList'])) {
|
|
DOMReady(function() {
|
|
if(
|
|
/iPad|iPhone|iPod/.test(navigator.userAgent)
|
|
&& !window.MSStream
|
|
&& doc.body.classList
|
|
) {
|
|
doc.body.classList.add('ios');
|
|
}
|
|
});
|
|
}
|
|
|
|
})();
|
|
|