|
|
@ -1,15 +1,14 @@ |
|
|
|
(function() { |
|
|
|
|
|
|
|
/* eslint-env browser */ |
|
|
|
(function () { |
|
|
|
// Space optimisations
|
|
|
|
var doc = document; |
|
|
|
var find = doc.querySelector.bind(doc); |
|
|
|
var create = doc.createElement.bind(doc); |
|
|
|
|
|
|
|
// Run callback when DOM is ready
|
|
|
|
function DOMReady(cb) { |
|
|
|
|
|
|
|
function domReady(cb) { |
|
|
|
// Run now if DOM has already loaded as we're loading async
|
|
|
|
if(['interactive', 'complete'].indexOf(doc.readyState) >= 0) { |
|
|
|
if (['interactive', 'complete'].indexOf(doc.readyState) >= 0) { |
|
|
|
cb(); |
|
|
|
|
|
|
|
// Otherwise wait for DOM
|
|
|
@ -20,36 +19,36 @@ |
|
|
|
|
|
|
|
// Feature detection
|
|
|
|
var supports = { |
|
|
|
test: function(features) { |
|
|
|
test: function (features) { |
|
|
|
var self = this; |
|
|
|
if(!features || !features.length) { |
|
|
|
if (!features || features.length < 1) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
return features.every(function(feature) { |
|
|
|
return features.every(function (feature) { |
|
|
|
return self.tests[feature]; |
|
|
|
}); |
|
|
|
}, |
|
|
|
tests: { |
|
|
|
localStorage: (function() { |
|
|
|
localStorage: (function () { |
|
|
|
try { |
|
|
|
localStorage.setItem('test', 'test'); |
|
|
|
localStorage.removeItem('test'); |
|
|
|
return true; |
|
|
|
} catch (e) { |
|
|
|
} catch (err) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
})(), |
|
|
|
inlineSVG: (function() { |
|
|
|
inlineSVG: (function () { |
|
|
|
var div = create('div'); |
|
|
|
div.innerHTML = '<svg/>'; |
|
|
|
return ( |
|
|
|
typeof SVGRect != 'undefined' |
|
|
|
&& div.firstChild |
|
|
|
&& div.firstChild.namespaceURI |
|
|
|
) == 'http://www.w3.org/2000/svg'; |
|
|
|
typeof SVGRect !== 'undefined' && |
|
|
|
div.firstChild && |
|
|
|
div.firstChild.namespaceURI |
|
|
|
) === 'http://www.w3.org/2000/svg'; |
|
|
|
})(), |
|
|
|
querySelector: typeof doc.querySelector === 'function', |
|
|
|
classList: (function() { |
|
|
|
classList: (function () { |
|
|
|
var div = create('div'); |
|
|
|
div.innerHTML = '<svg/>'; |
|
|
|
return 'classList' in div.firstChild; |
|
|
@ -71,33 +70,33 @@ |
|
|
|
activeClass: 'hearted', |
|
|
|
|
|
|
|
// Gets current node hash
|
|
|
|
getCurrentNode: function() { |
|
|
|
getCurrentNode: function () { |
|
|
|
var node = /^\/node\/([a-zA-Z0-9]+)/.exec(window.location.pathname); |
|
|
|
return node ? node[1] : node; |
|
|
|
}, |
|
|
|
|
|
|
|
// Gets current node title
|
|
|
|
getCurrentNodeTitle: function() { |
|
|
|
getCurrentNodeTitle: function () { |
|
|
|
return find('h2.node-title .name').innerText; |
|
|
|
}, |
|
|
|
|
|
|
|
// Gets hearted nodes
|
|
|
|
getHeartedNodes: function() { |
|
|
|
getHeartedNodes: function () { |
|
|
|
return JSON.parse(localStorage.getItem(favouriteNodes.storageKey)) || {}; |
|
|
|
}, |
|
|
|
|
|
|
|
// Saves hearted nodes
|
|
|
|
saveHeartedNodes: function(heartedNodes) { |
|
|
|
saveHeartedNodes: function (heartedNodes) { |
|
|
|
return localStorage.setItem(favouriteNodes.storageKey, JSON.stringify(heartedNodes)); |
|
|
|
}, |
|
|
|
|
|
|
|
// Checks if node is hearted
|
|
|
|
isHearted: function(node) { |
|
|
|
isHearted: function (node) { |
|
|
|
return typeof favouriteNodes.getHeartedNodes()[node] !== 'undefined'; |
|
|
|
}, |
|
|
|
|
|
|
|
// Heart node
|
|
|
|
heart: function(node) { |
|
|
|
heart: function (node) { |
|
|
|
var heartedNodes = favouriteNodes.getHeartedNodes(); |
|
|
|
heartedNodes[node] = favouriteNodes.getCurrentNodeTitle(); |
|
|
|
favouriteNodes.saveHeartedNodes(heartedNodes); |
|
|
@ -106,7 +105,7 @@ |
|
|
|
}, |
|
|
|
|
|
|
|
// Unheart node
|
|
|
|
unHeart: function(node) { |
|
|
|
unHeart: function (node) { |
|
|
|
var heartedNodes = favouriteNodes.getHeartedNodes(); |
|
|
|
delete heartedNodes[node]; |
|
|
|
favouriteNodes.saveHeartedNodes(heartedNodes); |
|
|
@ -115,44 +114,42 @@ |
|
|
|
}, |
|
|
|
|
|
|
|
// Get list of hearted nodes
|
|
|
|
updateHeartedNodesList: function() { |
|
|
|
updateHeartedNodesList: function () { |
|
|
|
var menu = find('.menu'); |
|
|
|
if(!menu) { |
|
|
|
if (!menu) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
var menuHTML = ''; |
|
|
|
var heartedNodes = favouriteNodes.getHeartedNodes(); |
|
|
|
var nodeHashes = Object.keys(heartedNodes); |
|
|
|
if(nodeHashes.length) { |
|
|
|
if (nodeHashes.length > 0) { |
|
|
|
menuHTML += '<ul>'; |
|
|
|
nodeHashes.forEach(function(node) { |
|
|
|
nodeHashes.forEach(function (node) { |
|
|
|
menuHTML += '<li><a href="/node/' + node + '">' + heartedNodes[node] + '</a></li>'; |
|
|
|
}); |
|
|
|
menuHTML += '</ul>'; |
|
|
|
} else { |
|
|
|
menuHTML += '<div class="empty">Click the heart next to a node\'s title on it\'s own page to save it here for easy access :)</div>'; |
|
|
|
} |
|
|
|
return menu.innerHTML = menuHTML; |
|
|
|
menu.innerHTML = menuHTML; |
|
|
|
return menu.innerHTML; |
|
|
|
}, |
|
|
|
|
|
|
|
// Load SVG, run callback when loaded
|
|
|
|
loadSVG: function(cb) { |
|
|
|
|
|
|
|
loadSVG: function (cb) { |
|
|
|
// Get heart SVG
|
|
|
|
var xhr = new XMLHttpRequest(); |
|
|
|
xhr.open('GET', favouriteNodes.heartPath); |
|
|
|
xhr.addEventListener('load', function() { |
|
|
|
xhr.addEventListener('load', function () { |
|
|
|
cb(xhr.responseText); |
|
|
|
}); |
|
|
|
xhr.send(); |
|
|
|
}, |
|
|
|
|
|
|
|
// Initiate node favouriting
|
|
|
|
init: function() { |
|
|
|
|
|
|
|
init: function () { |
|
|
|
// Start loading heart SVG before DOM
|
|
|
|
favouriteNodes.loadSVG(function(svg) { |
|
|
|
|
|
|
|
favouriteNodes.loadSVG(function (svg) { |
|
|
|
// Create heart SVG elem
|
|
|
|
var div = create('div'); |
|
|
|
div.innerHTML = svg; |
|
|
@ -160,16 +157,15 @@ |
|
|
|
|
|
|
|
// Show heart as active if we've already hearted this node
|
|
|
|
var node = favouriteNodes.getCurrentNode(); |
|
|
|
if(favouriteNodes.isHearted(node)) { |
|
|
|
if (favouriteNodes.isHearted(node)) { |
|
|
|
heartEl.classList.add(favouriteNodes.activeClass); |
|
|
|
} |
|
|
|
|
|
|
|
// Add click handler
|
|
|
|
heartEl.addEventListener('click', function() { |
|
|
|
|
|
|
|
heartEl.addEventListener('click', function () { |
|
|
|
// Heart/unheart node
|
|
|
|
var node = favouriteNodes.getCurrentNode(); |
|
|
|
if(favouriteNodes.isHearted(node)) { |
|
|
|
if (favouriteNodes.isHearted(node)) { |
|
|
|
heartEl.classList.remove(favouriteNodes.activeClass); |
|
|
|
favouriteNodes.unHeart(node); |
|
|
|
} else { |
|
|
@ -179,13 +175,13 @@ |
|
|
|
}); |
|
|
|
|
|
|
|
// Then inject into DOM when it's ready
|
|
|
|
DOMReady(function() { |
|
|
|
domReady(function () { |
|
|
|
var headerHeight = find('.title').offsetHeight; |
|
|
|
var headerBoxShadow = 3; |
|
|
|
|
|
|
|
// Heart
|
|
|
|
var titleEl = find('h2.node-title'); |
|
|
|
if(titleEl) { |
|
|
|
if (titleEl) { |
|
|
|
titleEl.insertBefore(heartEl, titleEl.firstChild); |
|
|
|
} |
|
|
|
|
|
|
@ -194,7 +190,7 @@ |
|
|
|
menuButton.classList.add('menu-button'); |
|
|
|
menuButton.style.height = headerHeight + 'px'; |
|
|
|
menuButton.innerHTML = svg; |
|
|
|
menuButton.addEventListener('click', function() { |
|
|
|
menuButton.addEventListener('click', function () { |
|
|
|
favouriteNodes.updateHeartedNodesList(); |
|
|
|
find('.menu').classList.toggle('active'); |
|
|
|
}); |
|
|
@ -212,8 +208,7 @@ |
|
|
|
|
|
|
|
// If current node is hearted
|
|
|
|
var node = favouriteNodes.getCurrentNode(); |
|
|
|
if(favouriteNodes.isHearted(node)) { |
|
|
|
|
|
|
|
if (favouriteNodes.isHearted(node)) { |
|
|
|
// Heart it again so we get the new name if it's updated
|
|
|
|
favouriteNodes.heart(node); |
|
|
|
} |
|
|
@ -221,19 +216,18 @@ |
|
|
|
}; |
|
|
|
|
|
|
|
// Service worker
|
|
|
|
if(supports.test(['serviceWorker', 'querySelector', 'classList'])) { |
|
|
|
|
|
|
|
if (supports.test(['serviceWorker', 'querySelector', 'classList'])) { |
|
|
|
// Register service worker
|
|
|
|
navigator.serviceWorker.register('/sw.js'); |
|
|
|
|
|
|
|
// Show cache message on stale pages
|
|
|
|
DOMReady(function() { |
|
|
|
if(window.cacheDate) { |
|
|
|
domReady(function () { |
|
|
|
if (window.cacheDate) { |
|
|
|
var offlineMessage = create('div'); |
|
|
|
offlineMessage.classList.add('cache-message'); |
|
|
|
offlineMessage.innerText = '*There seems to be an issue connecting to the server. This data is cached from ' + window.cacheDate; |
|
|
|
var main = find('main'); |
|
|
|
if(main) { |
|
|
|
if (main) { |
|
|
|
doc.body.classList.add('no-connection'); |
|
|
|
main.insertBefore(offlineMessage, main.firstChild); |
|
|
|
} |
|
|
@ -242,20 +236,19 @@ |
|
|
|
} |
|
|
|
|
|
|
|
// Init favourite nodes
|
|
|
|
if(supports.test(['localStorage', 'inlineSVG', 'querySelector', 'classList'])) { |
|
|
|
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 |
|
|
|
if (supports.test(['classList'])) { |
|
|
|
domReady(function () { |
|
|
|
if ( |
|
|
|
/iPad|iPhone|iPod/.test(navigator.userAgent) && |
|
|
|
!window.MSStream |
|
|
|
) { |
|
|
|
doc.body.classList.add('ios'); |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
})(); |
|
|
|