Luke Childs
8 years ago
15 changed files with 575 additions and 576 deletions
@ -1,4 +1,4 @@ |
|||||
module.exports = (req, res) => res.render('about.html', { |
module.exports = (req, res) => res.render('about.html', { |
||||
bodyClass: 'about', |
bodyClass: 'about', |
||||
pageTitle: 'About' |
pageTitle: 'About' |
||||
}); |
}); |
||||
|
@ -1,6 +1,7 @@ |
|||||
|
/* eslint no-unused-vars: ["error", { "argsIgnorePattern": "next" }] */ |
||||
module.exports = (err, req, res, next) => { |
module.exports = (err, req, res, next) => { |
||||
const statusCode = err.statusCode || 500; |
const statusCode = err.statusCode || 500; |
||||
const error = err.statusMessage || 'Something went wrong'; |
const error = err.statusMessage || 'Something went wrong'; |
||||
console.error(err); |
console.error(err); |
||||
res.status(statusCode).render('error.html', { error }); |
res.status(statusCode).render('error.html', {error}); |
||||
}; |
}; |
||||
|
@ -1,5 +1,5 @@ |
|||||
module.exports = (req, res) => { |
module.exports = (req, res) => { |
||||
const statusCode = 404; |
const statusCode = 404; |
||||
const error = '404 Not Found'; |
const error = '404 Not Found'; |
||||
res.status(statusCode).render('error.html', { error }); |
res.status(statusCode).render('error.html', {error}); |
||||
}; |
}; |
||||
|
@ -1,8 +1,8 @@ |
|||||
module.exports = { |
module.exports = { |
||||
listing: require('./listing'), |
listing: require('./listing'), |
||||
node: require('./node'), |
node: require('./node'), |
||||
about: require('./about'), |
about: require('./about'), |
||||
noConnection: require('./no-connection'), |
noConnection: require('./no-connection'), |
||||
error: require('./error'), |
error: require('./error'), |
||||
error404: require('./error404') |
error404: require('./error404') |
||||
}; |
}; |
||||
|
@ -1,33 +1,32 @@ |
|||||
const tor = require('../lib/tor'); |
const tor = require('../lib/tor'); |
||||
|
|
||||
module.exports = (req, res, next) => { |
module.exports = (req, res, next) => { |
||||
|
let title = 'Top nodes by consensus weight'; |
||||
|
const query = { |
||||
|
limit: 10 |
||||
|
}; |
||||
|
if (req.query.s) { |
||||
|
title = `Search results for "${req.query.s}":`; |
||||
|
query.search = req.query.s; |
||||
|
} else { |
||||
|
query.order = '-consensus_weight'; |
||||
|
query.running = true; |
||||
|
} |
||||
|
if (req.query.p) { |
||||
|
query.offset = (query.limit * req.query.p) - query.limit; |
||||
|
} |
||||
|
|
||||
let title = 'Top nodes by consensus weight'; |
tor.listNodes(query) |
||||
const query = { |
.then(nodes => res.render('listing.html', { |
||||
limit: 10 |
pageTitle: req.query.s ? `Search: ${req.query.s}` : false, |
||||
}; |
title: title, |
||||
if(req.query.s) { |
nodes: nodes, |
||||
title = `Search results for "${req.query.s}":`; |
numOfNodes: query.limit |
||||
query.search = req.query.s; |
})) |
||||
} else { |
.catch(err => { |
||||
query.order = '-consensus_weight'; |
if (err.statusCode === 400 && req.query.s) { |
||||
query.running = true; |
err.statusMessage = 'Bad Search Query'; |
||||
} |
} |
||||
if(req.query.p) { |
next(err); |
||||
query.offset = (query.limit * req.query.p) - query.limit; |
}); |
||||
} |
}; |
||||
|
|
||||
tor.listNodes(query) |
|
||||
.then(nodes => res.render('listing.html', { |
|
||||
pageTitle: req.query.s ? `Search: ${req.query.s}` : false, |
|
||||
title: title, |
|
||||
nodes: nodes, |
|
||||
numOfNodes: query.limit |
|
||||
})) |
|
||||
.catch(err => { |
|
||||
if(err.statusCode == 400 && req.query.s) { |
|
||||
err.statusMessage = 'Bad Search Query'; |
|
||||
} |
|
||||
next(err); |
|
||||
}); |
|
||||
} |
|
||||
|
@ -1,4 +1,4 @@ |
|||||
module.exports = (req, res) => res.render('no-connection.html', { |
module.exports = (req, res) => res.render('no-connection.html', { |
||||
bodyClass: 'no-connection', |
bodyClass: 'no-connection', |
||||
pageTitle: 'No Connection' |
pageTitle: 'No Connection' |
||||
}); |
}); |
||||
|
@ -1,30 +1,30 @@ |
|||||
const tor = require('../lib/tor'); |
const tor = require('../lib/tor'); |
||||
const bandwidthChart = require('../lib/bandwidth-chart'); |
const bandwidthChart = require('../lib/bandwidth-chart'); |
||||
|
|
||||
module.exports = (req, res, next) => { |
module.exports = (req, res, next) => { |
||||
Promise.all([ |
Promise.all([ |
||||
tor.node(req.params.id), |
tor.node(req.params.id), |
||||
tor.bandwidth(req.params.id) |
tor.bandwidth(req.params.id) |
||||
]) |
]) |
||||
.then(data => { |
.then(data => { |
||||
// Throw 404 if node doesn't exist
|
// Throw 404 if node doesn't exist
|
||||
if(!data[0]) { |
if (!data[0]) { |
||||
const err = new Error('Node doesn\'t exist'); |
const err = new Error('Node doesn\'t exist'); |
||||
err.statusMessage = err.message; |
err.statusMessage = err.message; |
||||
err.statusCode = 404; |
err.statusCode = 404; |
||||
throw err; |
throw err; |
||||
} |
} |
||||
|
|
||||
res.render('node.html', { |
res.render('node.html', { |
||||
pageTitle: `${data[0].type}: ${data[0].nickname}`, |
pageTitle: `${data[0].type}: ${data[0].nickname}`, |
||||
node: data[0], |
node: data[0], |
||||
bandwidth: bandwidthChart(data[1]) |
bandwidth: bandwidthChart(data[1]) |
||||
}) |
}); |
||||
}) |
}) |
||||
.catch(err => { |
.catch(err => { |
||||
if(err.statusCode == 400) { |
if (err.statusCode === 400) { |
||||
err.statusMessage = 'Invalid node'; |
err.statusMessage = 'Invalid node'; |
||||
} |
} |
||||
next(err); |
next(err); |
||||
}); |
}); |
||||
} |
}; |
||||
|
@ -1,51 +1,49 @@ |
|||||
const chart = require('ascii-chart'); |
const chart = require('ascii-chart'); |
||||
|
|
||||
function pointsFromBandwidthData(values, numPoints) { |
function pointsFromBandwidthData(values, numPoints) { |
||||
|
// Define vars
|
||||
|
const len = values.length; |
||||
|
const points = []; |
||||
|
let i = 0; |
||||
|
let size; |
||||
|
|
||||
// Define vars
|
// Split values into n points
|
||||
const len = values.length; |
if (numPoints < 2) { |
||||
const points = []; |
points.push(values); |
||||
let i = 0; |
} else { |
||||
let size; |
if (len % numPoints === 0) { |
||||
|
size = Math.floor(len / numPoints); |
||||
|
while (i < len) { |
||||
|
points.push(values.slice(i, i += size)); |
||||
|
} |
||||
|
} |
||||
|
while (i < len) { |
||||
|
size = Math.ceil((len - i) / numPoints--); |
||||
|
points.push(values.slice(i, i += size)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
// Split values into n points
|
// Return points
|
||||
if(numPoints < 2) { |
return points |
||||
points.push(values); |
|
||||
} else { |
|
||||
if(len % numPoints === 0) { |
|
||||
size = Math.floor(len / numPoints); |
|
||||
while (i < len) { |
|
||||
points.push(values.slice(i, i += size)); |
|
||||
} |
|
||||
} |
|
||||
while (i < len) { |
|
||||
size = Math.ceil((len - i) / numPoints--); |
|
||||
points.push(values.slice(i, i += size)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Return points
|
// Calculate average value of each point
|
||||
return points |
.map(point => Math.round(point.reduce((a, b) => a + b) / point.length)) |
||||
|
|
||||
// Calculate average value of each point
|
// Convert bytes to megabytes
|
||||
.map(point => Math.round(point.reduce((a,b) => a + b) / point.length)) |
.map(bytes => Number((bytes / 1000000).toPrecision(3))); |
||||
|
|
||||
// Convert bytes to megabytes
|
|
||||
.map(bytes => Number((bytes / 1000000).toPrecision(3))); |
|
||||
} |
} |
||||
|
|
||||
module.exports = values => { |
module.exports = values => { |
||||
if(values && values.length) { |
if (!values || values.length < 1) { |
||||
const points = pointsFromBandwidthData(values, 57); |
return ''; |
||||
return chart(points, { |
} |
||||
width: 120, |
const points = pointsFromBandwidthData(values, 57); |
||||
height: 20, |
return chart(points, { |
||||
padding: 0, |
width: 120, |
||||
pointChar: '*', |
height: 20, |
||||
negativePointChar: '.', |
padding: 0, |
||||
axisChar: '.' |
pointChar: '*', |
||||
}); |
negativePointChar: '.', |
||||
} else { |
axisChar: '.' |
||||
return ''; |
}); |
||||
} |
}; |
||||
} |
|
||||
|
@ -1,20 +1,21 @@ |
|||||
const minify = require('express-minify'); |
const minify = require('express-minify'); |
||||
const minifyHTML = require('express-minify-html'); |
const minifyHTML = require('express-minify-html'); |
||||
const CleanCSS = require('clean-css'); |
const CleanCSS = require('clean-css'); |
||||
const cleanCSS = new CleanCSS(); |
|
||||
|
const cleanCSS = new CleanCSS(); |
||||
|
|
||||
module.exports = [ |
module.exports = [ |
||||
minify({ |
minify({ |
||||
cssmin: source => cleanCSS.minify(source).styles |
cssmin: source => cleanCSS.minify(source).styles |
||||
}), |
}), |
||||
minifyHTML({ |
minifyHTML({ |
||||
htmlMinifier: { |
htmlMinifier: { |
||||
removeComments: true, |
removeComments: true, |
||||
collapseWhitespace: true, |
collapseWhitespace: true, |
||||
collapseBooleanAttributes: true, |
collapseBooleanAttributes: true, |
||||
removeAttributeQuotes: true, |
removeAttributeQuotes: true, |
||||
removeEmptyAttributes: true, |
removeEmptyAttributes: true, |
||||
removeOptionalTags: true |
removeOptionalTags: true |
||||
} |
} |
||||
}) |
}) |
||||
]; |
]; |
||||
|
@ -1,70 +1,78 @@ |
|||||
const prettyBytes = require('pretty-bytes'); |
|
||||
const moment = require('moment'); |
|
||||
const querystring = require('querystring'); |
const querystring = require('querystring'); |
||||
|
const prettyBytes = require('pretty-bytes'); |
||||
|
const moment = require('moment'); |
||||
|
|
||||
function humanTimeAgo(utcDate) { |
function humanTimeAgo(utcDate) { |
||||
const diff = moment.utc().diff(moment.utc(utcDate)); |
const diff = moment.utc().diff(moment.utc(utcDate)); |
||||
const uptime = {}; |
const uptime = {}; |
||||
|
|
||||
uptime.s = Math.round(diff / 1000); |
uptime.s = Math.round(diff / 1000); |
||||
uptime.m = Math.floor(uptime.s / 60); |
uptime.m = Math.floor(uptime.s / 60); |
||||
uptime.h = Math.floor(uptime.m / 60); |
uptime.h = Math.floor(uptime.m / 60); |
||||
uptime.d = Math.floor(uptime.h / 24); |
uptime.d = Math.floor(uptime.h / 24); |
||||
|
|
||||
uptime.s %= 60; |
uptime.s %= 60; |
||||
uptime.m %= 60; |
uptime.m %= 60; |
||||
uptime.h %= 24; |
uptime.h %= 24; |
||||
|
|
||||
let readableUptime = ''; |
let readableUptime = ''; |
||||
readableUptime += uptime.d ? ` ${uptime.d}d` : ''; |
readableUptime += uptime.d ? ` ${uptime.d}d` : ''; |
||||
readableUptime += uptime.h ? ` ${uptime.h}h` : ''; |
readableUptime += uptime.h ? ` ${uptime.h}h` : ''; |
||||
readableUptime += !uptime.d || !uptime.h && uptime.m ? ` ${uptime.m}m` : ''; |
if ((!uptime.d || !uptime.h) && uptime.m) { |
||||
|
readableUptime += ` ${uptime.m}m`; |
||||
|
} |
||||
|
|
||||
return readableUptime.trim(); |
return readableUptime.trim(); |
||||
} |
} |
||||
|
|
||||
const filters = { |
const filters = { |
||||
bandwidth: node => `${prettyBytes(node.advertised_bandwidth)}/s`, |
bandwidth: node => `${prettyBytes(node.advertised_bandwidth)}/s`, |
||||
uptime: node => { |
uptime: node => { |
||||
|
|
||||
// Check node is up
|
// Check node is up
|
||||
if(!node.running) { |
if (!node.running) { |
||||
return 'Down'; |
return 'Down'; |
||||
} |
} |
||||
|
|
||||
// Check uptime
|
// Check uptime
|
||||
return humanTimeAgo(node.last_restarted); |
return humanTimeAgo(node.last_restarted); |
||||
}, |
}, |
||||
pagination: (req, direction) => { |
pagination: (req, direction) => { |
||||
|
|
||||
// Clone query string
|
// Clone query string
|
||||
const query = Object.assign({}, req.query); |
const query = Object.assign({}, req.query); |
||||
|
|
||||
// Set page as 1 by default
|
// Set page as 1 by default
|
||||
query.p = query.p ? query.p : 1; |
query.p = query.p ? query.p : 1; |
||||
|
|
||||
// Update page
|
// Update page
|
||||
if(direction == 'next') { |
if (direction === 'next') { |
||||
query.p++; |
query.p++; |
||||
} else if(direction == 'prev') { |
} else if (direction === 'prev') { |
||||
query.p--; |
query.p--; |
||||
} |
} |
||||
|
|
||||
// Don't add p var if it's page 1
|
// Don't add p var if it's page 1
|
||||
query.p == 1 && delete query.p |
if (query.p === 1) { |
||||
|
delete query.p; |
||||
|
} |
||||
|
|
||||
// Encode query string
|
// Encode query string
|
||||
const queryString = querystring.encode(query); |
const queryString = querystring.encode(query); |
||||
return queryString ? `/?${queryString}` : '/'; |
return queryString ? `/?${queryString}` : '/'; |
||||
}, |
}, |
||||
name: node => node.nickname |
name: node => { |
||||
|| node.fingerprint && node.fingerprint.slice(0, 8) |
let name = ''; |
||||
|| node.hashed_fingerprint && node.hashed_fingerprint.slice(0, 8), |
if (node.nickname) { |
||||
status: node => node.running ? |
name = node.nickname; |
||||
|
} else if (node.fingerprint || node.hashed_fingerprint) { |
||||
|
name = (node.fingerprint || node.hashed_fingerprint).slice(0, 8); |
||||
|
} |
||||
|
return name; |
||||
|
}, |
||||
|
status: node => node.running ? |
||||
`Up for ${humanTimeAgo(node.last_restarted)}` : |
`Up for ${humanTimeAgo(node.last_restarted)}` : |
||||
`Down for ${humanTimeAgo(node.last_seen)}` |
`Down for ${humanTimeAgo(node.last_seen)}` |
||||
}; |
}; |
||||
|
|
||||
module.exports = app => Object.keys(filters).forEach(filter => { |
module.exports = app => Object.keys(filters).forEach(filter => { |
||||
app.settings.nunjucksEnv.addFilter(filter, filters[filter]) |
app.settings.nunjucksEnv.addFilter(filter, filters[filter]); |
||||
}); |
}); |
||||
|
@ -1,5 +1,5 @@ |
|||||
module.exports = (req, res, next) => { |
module.exports = (req, res, next) => { |
||||
res.locals.req = req; |
res.locals.req = req; |
||||
res.locals.res = res; |
res.locals.res = res; |
||||
next(); |
next(); |
||||
} |
}; |
||||
|
@ -1,43 +1,48 @@ |
|||||
const Onionoo = require('onionoo'); |
const Onionoo = require('onionoo'); |
||||
|
|
||||
const onionoo = new Onionoo(); |
const onionoo = new Onionoo(); |
||||
|
|
||||
|
const setNodeType = type => node => { |
||||
|
node.type = type; |
||||
|
return node; |
||||
|
}; |
||||
|
|
||||
module.exports = { |
module.exports = { |
||||
listNodes: query => { |
listNodes: query => { |
||||
return onionoo |
return onionoo |
||||
.details(query) |
.details(query) |
||||
.then(response => { |
.then(response => { |
||||
const details = response.body; |
const details = response.body; |
||||
details.relays.forEach(node => node.type = 'relay'); |
details.relays.map(setNodeType('relay')); |
||||
details.bridges.forEach(node => node.type = 'bridge'); |
details.bridges.map(setNodeType('bridge')); |
||||
return details.relays.concat(details.bridges); |
return details.relays.concat(details.bridges); |
||||
}); |
}); |
||||
}, |
}, |
||||
node: id => { |
node: id => { |
||||
return onionoo |
return onionoo |
||||
.details({ lookup: id }) |
.details({lookup: id}) |
||||
.then(response => { |
.then(response => { |
||||
const details = response.body; |
const details = response.body; |
||||
if(details.relays[0]) { |
if (details.relays[0]) { |
||||
details.relays[0].type = 'relay'; |
details.relays[0].type = 'relay'; |
||||
return details.relays[0]; |
return details.relays[0]; |
||||
} else if(details.bridges[0]) { |
} else if (details.bridges[0]) { |
||||
details.bridges[0].type = 'bridge'; |
details.bridges[0].type = 'bridge'; |
||||
return details.bridges[0]; |
return details.bridges[0]; |
||||
} |
} |
||||
}); |
}); |
||||
}, |
}, |
||||
bandwidth: id => { |
bandwidth: id => { |
||||
return onionoo |
return onionoo |
||||
.bandwidth({ lookup: id }) |
.bandwidth({lookup: id}) |
||||
.then(response => { |
.then(response => { |
||||
const bandwidth = response.body; |
const bandwidth = response.body; |
||||
try { |
try { |
||||
const lastMonth = bandwidth.relays[0].write_history['1_month']; |
const lastMonth = bandwidth.relays[0].write_history['1_month']; |
||||
return lastMonth.values.map(value => value * lastMonth.factor) |
return lastMonth.values.map(value => value * lastMonth.factor); |
||||
} |
} catch (err) { |
||||
catch(e) { |
return []; |
||||
return []; |
} |
||||
} |
}); |
||||
}); |
} |
||||
} |
|
||||
}; |
}; |
||||
|
@ -1,261 +1,254 @@ |
|||||
(function() { |
/* eslint-env browser */ |
||||
|
(function () { |
||||
// Space optimisations
|
// Space optimisations
|
||||
var doc = document; |
var doc = document; |
||||
var find = doc.querySelector.bind(doc); |
var find = doc.querySelector.bind(doc); |
||||
var create = doc.createElement.bind(doc); |
var create = doc.createElement.bind(doc); |
||||
|
|
||||
// Run callback when DOM is ready
|
// Run callback when DOM is ready
|
||||
function DOMReady(cb) { |
function domReady(cb) { |
||||
|
// Run now if DOM has already loaded as we're loading async
|
||||
// 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(); |
||||
cb(); |
|
||||
|
// Otherwise wait for DOM
|
||||
// Otherwise wait for DOM
|
} else { |
||||
} else { |
doc.addEventListener('DOMContentLoaded', cb); |
||||
doc.addEventListener('DOMContentLoaded', cb); |
} |
||||
} |
} |
||||
} |
|
||||
|
// Feature detection
|
||||
// Feature detection
|
var supports = { |
||||
var supports = { |
test: function (features) { |
||||
test: function(features) { |
var self = this; |
||||
var self = this; |
if (!features || features.length < 1) { |
||||
if(!features || !features.length) { |
return false; |
||||
return false; |
} |
||||
} |
return features.every(function (feature) { |
||||
return features.every(function(feature) { |
return self.tests[feature]; |
||||
return self.tests[feature]; |
}); |
||||
}); |
}, |
||||
}, |
tests: { |
||||
tests: { |
localStorage: (function () { |
||||
localStorage: (function() { |
try { |
||||
try { |
localStorage.setItem('test', 'test'); |
||||
localStorage.setItem('test', 'test'); |
localStorage.removeItem('test'); |
||||
localStorage.removeItem('test'); |
return true; |
||||
return true; |
} catch (err) { |
||||
} catch (e) { |
return false; |
||||
return false; |
} |
||||
} |
})(), |
||||
})(), |
inlineSVG: (function () { |
||||
inlineSVG: (function() { |
var div = create('div'); |
||||
var div = create('div'); |
div.innerHTML = '<svg/>'; |
||||
div.innerHTML = '<svg/>'; |
return ( |
||||
return ( |
typeof SVGRect !== 'undefined' && |
||||
typeof SVGRect != 'undefined' |
div.firstChild && |
||||
&& div.firstChild |
div.firstChild.namespaceURI |
||||
&& div.firstChild.namespaceURI |
) === 'http://www.w3.org/2000/svg'; |
||||
) == 'http://www.w3.org/2000/svg'; |
})(), |
||||
})(), |
querySelector: typeof doc.querySelector === 'function', |
||||
querySelector: typeof doc.querySelector === 'function', |
classList: (function () { |
||||
classList: (function() { |
var div = create('div'); |
||||
var div = create('div'); |
div.innerHTML = '<svg/>'; |
||||
div.innerHTML = '<svg/>'; |
return 'classList' in div.firstChild; |
||||
return 'classList' in div.firstChild; |
})(), |
||||
})(), |
serviceWorker: 'serviceWorker' in navigator |
||||
serviceWorker: 'serviceWorker' in navigator |
} |
||||
} |
}; |
||||
}; |
|
||||
|
// Favourite nodes
|
||||
// Favourite nodes
|
var favouriteNodes = { |
||||
var favouriteNodes = { |
|
||||
|
// Key used in localStorage
|
||||
// Key used in localStorage
|
storageKey: 'heartedNodes', |
||||
storageKey: 'heartedNodes', |
|
||||
|
// Url to heart SVG
|
||||
// Url to heart SVG
|
heartPath: '/assets/heart.svg', |
||||
heartPath: '/assets/heart.svg', |
|
||||
|
// Class added to heart SVG element when active
|
||||
// Class added to heart SVG element when active
|
activeClass: 'hearted', |
||||
activeClass: 'hearted', |
|
||||
|
// Gets current node hash
|
||||
// Gets current node hash
|
getCurrentNode: function () { |
||||
getCurrentNode: function() { |
var node = /^\/node\/([a-zA-Z0-9]+)/.exec(window.location.pathname); |
||||
var node = /^\/node\/([a-zA-Z0-9]+)/.exec(window.location.pathname); |
return node ? node[1] : node; |
||||
return node ? node[1] : node; |
}, |
||||
}, |
|
||||
|
// Gets current node title
|
||||
// Gets current node title
|
getCurrentNodeTitle: function () { |
||||
getCurrentNodeTitle: function() { |
return find('h2.node-title .name').innerText; |
||||
return find('h2.node-title .name').innerText; |
}, |
||||
}, |
|
||||
|
// Gets hearted nodes
|
||||
// Gets hearted nodes
|
getHeartedNodes: function () { |
||||
getHeartedNodes: function() { |
return JSON.parse(localStorage.getItem(favouriteNodes.storageKey)) || {}; |
||||
return JSON.parse(localStorage.getItem(favouriteNodes.storageKey)) || {}; |
}, |
||||
}, |
|
||||
|
// Saves hearted nodes
|
||||
// Saves hearted nodes
|
saveHeartedNodes: function (heartedNodes) { |
||||
saveHeartedNodes: function(heartedNodes) { |
return localStorage.setItem(favouriteNodes.storageKey, JSON.stringify(heartedNodes)); |
||||
return localStorage.setItem(favouriteNodes.storageKey, JSON.stringify(heartedNodes)); |
}, |
||||
}, |
|
||||
|
// Checks if node is hearted
|
||||
// Checks if node is hearted
|
isHearted: function (node) { |
||||
isHearted: function(node) { |
return typeof favouriteNodes.getHeartedNodes()[node] !== 'undefined'; |
||||
return typeof favouriteNodes.getHeartedNodes()[node] !== 'undefined'; |
}, |
||||
}, |
|
||||
|
// Heart node
|
||||
// Heart node
|
heart: function (node) { |
||||
heart: function(node) { |
var heartedNodes = favouriteNodes.getHeartedNodes(); |
||||
var heartedNodes = favouriteNodes.getHeartedNodes(); |
heartedNodes[node] = favouriteNodes.getCurrentNodeTitle(); |
||||
heartedNodes[node] = favouriteNodes.getCurrentNodeTitle(); |
favouriteNodes.saveHeartedNodes(heartedNodes); |
||||
favouriteNodes.saveHeartedNodes(heartedNodes); |
favouriteNodes.updateHeartedNodesList(); |
||||
favouriteNodes.updateHeartedNodesList(); |
return heartedNodes; |
||||
return heartedNodes; |
}, |
||||
}, |
|
||||
|
// Unheart node
|
||||
// Unheart node
|
unHeart: function (node) { |
||||
unHeart: function(node) { |
var heartedNodes = favouriteNodes.getHeartedNodes(); |
||||
var heartedNodes = favouriteNodes.getHeartedNodes(); |
delete heartedNodes[node]; |
||||
delete heartedNodes[node]; |
favouriteNodes.saveHeartedNodes(heartedNodes); |
||||
favouriteNodes.saveHeartedNodes(heartedNodes); |
favouriteNodes.updateHeartedNodesList(); |
||||
favouriteNodes.updateHeartedNodesList(); |
return heartedNodes; |
||||
return heartedNodes; |
}, |
||||
}, |
|
||||
|
// Get list of hearted nodes
|
||||
// Get list of hearted nodes
|
updateHeartedNodesList: function () { |
||||
updateHeartedNodesList: function() { |
var menu = find('.menu'); |
||||
var menu = find('.menu'); |
if (!menu) { |
||||
if(!menu) { |
return false; |
||||
return false; |
} |
||||
} |
var menuHTML = ''; |
||||
var menuHTML = ''; |
var heartedNodes = favouriteNodes.getHeartedNodes(); |
||||
var heartedNodes = favouriteNodes.getHeartedNodes(); |
var nodeHashes = Object.keys(heartedNodes); |
||||
var nodeHashes = Object.keys(heartedNodes); |
if (nodeHashes.length > 0) { |
||||
if(nodeHashes.length) { |
menuHTML += '<ul>'; |
||||
menuHTML += '<ul>'; |
nodeHashes.forEach(function (node) { |
||||
nodeHashes.forEach(function(node) { |
menuHTML += '<li><a href="/node/' + node + '">' + heartedNodes[node] + '</a></li>'; |
||||
menuHTML += '<li><a href="/node/' + node + '">' + heartedNodes[node] + '</a></li>'; |
}); |
||||
}); |
menuHTML += '</ul>'; |
||||
menuHTML += '</ul>'; |
} else { |
||||
} 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>'; |
||||
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>'; |
} |
||||
} |
menu.innerHTML = menuHTML; |
||||
return menu.innerHTML = menuHTML; |
return menu.innerHTML; |
||||
}, |
}, |
||||
|
|
||||
// Load SVG, run callback when loaded
|
// Load SVG, run callback when loaded
|
||||
loadSVG: function(cb) { |
loadSVG: function (cb) { |
||||
|
// Get heart SVG
|
||||
// Get heart SVG
|
var xhr = new XMLHttpRequest(); |
||||
var xhr = new XMLHttpRequest(); |
xhr.open('GET', favouriteNodes.heartPath); |
||||
xhr.open('GET', favouriteNodes.heartPath); |
xhr.addEventListener('load', function () { |
||||
xhr.addEventListener('load', function() { |
cb(xhr.responseText); |
||||
cb(xhr.responseText); |
}); |
||||
}); |
xhr.send(); |
||||
xhr.send(); |
}, |
||||
}, |
|
||||
|
// Initiate node favouriting
|
||||
// Initiate node favouriting
|
init: function () { |
||||
init: function() { |
// Start loading heart SVG before DOM
|
||||
|
favouriteNodes.loadSVG(function (svg) { |
||||
// Start loading heart SVG before DOM
|
// Create heart SVG elem
|
||||
favouriteNodes.loadSVG(function(svg) { |
var div = create('div'); |
||||
|
div.innerHTML = svg; |
||||
// Create heart SVG elem
|
var heartEl = div.firstChild; |
||||
var div = create('div'); |
|
||||
div.innerHTML = svg; |
// Show heart as active if we've already hearted this node
|
||||
var heartEl = div.firstChild; |
var node = favouriteNodes.getCurrentNode(); |
||||
|
if (favouriteNodes.isHearted(node)) { |
||||
// Show heart as active if we've already hearted this node
|
heartEl.classList.add(favouriteNodes.activeClass); |
||||
var node = favouriteNodes.getCurrentNode(); |
} |
||||
if(favouriteNodes.isHearted(node)) { |
|
||||
heartEl.classList.add(favouriteNodes.activeClass); |
// Add click handler
|
||||
} |
heartEl.addEventListener('click', function () { |
||||
|
// Heart/unheart node
|
||||
// Add click handler
|
var node = favouriteNodes.getCurrentNode(); |
||||
heartEl.addEventListener('click', function() { |
if (favouriteNodes.isHearted(node)) { |
||||
|
heartEl.classList.remove(favouriteNodes.activeClass); |
||||
// Heart/unheart node
|
favouriteNodes.unHeart(node); |
||||
var node = favouriteNodes.getCurrentNode(); |
} else { |
||||
if(favouriteNodes.isHearted(node)) { |
heartEl.classList.add(favouriteNodes.activeClass); |
||||
heartEl.classList.remove(favouriteNodes.activeClass); |
favouriteNodes.heart(node); |
||||
favouriteNodes.unHeart(node); |
} |
||||
} else { |
}); |
||||
heartEl.classList.add(favouriteNodes.activeClass); |
|
||||
favouriteNodes.heart(node); |
// Then inject into DOM when it's ready
|
||||
} |
domReady(function () { |
||||
}); |
var headerHeight = find('.title').offsetHeight; |
||||
|
var headerBoxShadow = 3; |
||||
// Then inject into DOM when it's ready
|
|
||||
DOMReady(function() { |
// Heart
|
||||
var headerHeight = find('.title').offsetHeight; |
var titleEl = find('h2.node-title'); |
||||
var headerBoxShadow = 3; |
if (titleEl) { |
||||
|
titleEl.insertBefore(heartEl, titleEl.firstChild); |
||||
// Heart
|
} |
||||
var titleEl = find('h2.node-title'); |
|
||||
if(titleEl) { |
// Menu button
|
||||
titleEl.insertBefore(heartEl, titleEl.firstChild); |
var menuButton = create('div'); |
||||
} |
menuButton.classList.add('menu-button'); |
||||
|
menuButton.style.height = headerHeight + 'px'; |
||||
// Menu button
|
menuButton.innerHTML = svg; |
||||
var menuButton = create('div'); |
menuButton.addEventListener('click', function () { |
||||
menuButton.classList.add('menu-button'); |
favouriteNodes.updateHeartedNodesList(); |
||||
menuButton.style.height = headerHeight + 'px'; |
find('.menu').classList.toggle('active'); |
||||
menuButton.innerHTML = svg; |
}); |
||||
menuButton.addEventListener('click', function() { |
find('header .wrapper').appendChild(menuButton); |
||||
favouriteNodes.updateHeartedNodesList(); |
|
||||
find('.menu').classList.toggle('active'); |
// Menu
|
||||
}); |
var menu = create('div'); |
||||
find('header .wrapper').appendChild(menuButton); |
menu.classList.add('menu'); |
||||
|
menu.style.top = (headerHeight + headerBoxShadow) + 'px'; |
||||
// Menu
|
menu.style.height = 'calc(100% - ' + (headerHeight + headerBoxShadow) + 'px)'; |
||||
var menu = create('div'); |
document.body.appendChild(menu); |
||||
menu.classList.add('menu'); |
favouriteNodes.updateHeartedNodesList(); |
||||
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
|
||||
// If current node is hearted
|
favouriteNodes.heart(node); |
||||
var node = favouriteNodes.getCurrentNode(); |
} |
||||
if(favouriteNodes.isHearted(node)) { |
} |
||||
|
}; |
||||
// Heart it again so we get the new name if it's updated
|
|
||||
favouriteNodes.heart(node); |
// Service worker
|
||||
} |
if (supports.test(['serviceWorker', 'querySelector', 'classList'])) { |
||||
} |
// Register service worker
|
||||
}; |
navigator.serviceWorker.register('/sw.js'); |
||||
|
|
||||
// Service worker
|
// Show cache message on stale pages
|
||||
if(supports.test(['serviceWorker', 'querySelector', 'classList'])) { |
domReady(function () { |
||||
|
if (window.cacheDate) { |
||||
// Register service worker
|
var offlineMessage = create('div'); |
||||
navigator.serviceWorker.register('/sw.js'); |
offlineMessage.classList.add('cache-message'); |
||||
|
offlineMessage.innerText = '*There seems to be an issue connecting to the server. This data is cached from ' + window.cacheDate; |
||||
// Show cache message on stale pages
|
var main = find('main'); |
||||
DOMReady(function() { |
if (main) { |
||||
if(window.cacheDate) { |
doc.body.classList.add('no-connection'); |
||||
var offlineMessage = create('div'); |
main.insertBefore(offlineMessage, main.firstChild); |
||||
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) { |
} |
||||
doc.body.classList.add('no-connection'); |
|
||||
main.insertBefore(offlineMessage, main.firstChild); |
// Init favourite nodes
|
||||
} |
if (supports.test(['localStorage', 'inlineSVG', 'querySelector', 'classList'])) { |
||||
} |
favouriteNodes.init(); |
||||
}); |
} |
||||
} |
|
||||
|
// Add ios class to body on iOS devices
|
||||
// Init favourite nodes
|
if (supports.test(['classList'])) { |
||||
if(supports.test(['localStorage', 'inlineSVG', 'querySelector', 'classList'])) { |
domReady(function () { |
||||
favouriteNodes.init(); |
if ( |
||||
} |
/iPad|iPhone|iPod/.test(navigator.userAgent) && |
||||
|
!window.MSStream |
||||
// Add ios class to body on iOS devices
|
) { |
||||
if(supports.test(['classList'])) { |
doc.body.classList.add('ios'); |
||||
DOMReady(function() { |
} |
||||
if( |
}); |
||||
/iPad|iPhone|iPod/.test(navigator.userAgent) |
} |
||||
&& !window.MSStream |
|
||||
) { |
|
||||
doc.body.classList.add('ios'); |
|
||||
} |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
})(); |
})(); |
||||
|
@ -1,92 +1,85 @@ |
|||||
var cacheName = 'onionite-cache-v1'; |
/* eslint-env browser */ |
||||
var offlineUrl = '/no-connection'; |
var cacheName = 'onionite-cache-v1'; |
||||
|
var offlineUrl = '/no-connection'; |
||||
|
|
||||
// Install
|
// Install
|
||||
self.addEventListener('install', function(event) { |
self.addEventListener('install', function (event) { |
||||
|
// Cache assets
|
||||
// Cache assets
|
event.waitUntil( |
||||
event.waitUntil( |
caches.open(cacheName) |
||||
caches.open(cacheName) |
.then(function (cache) { |
||||
.then(function(cache) { |
return cache.addAll([ |
||||
return cache.addAll([ |
offlineUrl, |
||||
offlineUrl, |
'/', |
||||
'/', |
'/about', |
||||
'/about', |
'/assets/style.css', |
||||
'/assets/style.css', |
'/assets/enhancements.js?v2', |
||||
'/assets/enhancements.js?v2', |
'/assets/iconfont.woff', |
||||
'/assets/iconfont.woff', |
'/assets/heart.svg' |
||||
'/assets/heart.svg' |
]); |
||||
]); |
}) |
||||
}) |
); |
||||
); |
|
||||
}); |
}); |
||||
|
|
||||
// Fetch
|
// Fetch
|
||||
self.addEventListener('fetch', function(event) { |
self.addEventListener('fetch', function (event) { |
||||
var requestUrl = new URL(event.request.url); |
var requestUrl = new URL(event.request.url); |
||||
|
|
||||
// Only do stuff with our own urls
|
// Only do stuff with our own urls
|
||||
if(requestUrl.origin !== location.origin) { |
if (requestUrl.origin !== location.origin) { |
||||
return; |
return; |
||||
} |
} |
||||
|
|
||||
// Try cache first for assets
|
// Try cache first for assets
|
||||
if(requestUrl.pathname.startsWith('/assets/')) { |
if (requestUrl.pathname.startsWith('/assets/')) { |
||||
event.respondWith( |
event.respondWith( |
||||
caches.match(event.request) |
caches.match(event.request) |
||||
.then(function(response) { |
.then(function (response) { |
||||
|
// If we don't have it make the request
|
||||
// If we don't have it make the request
|
return response || fetch(event.request); |
||||
return response || fetch(event.request); |
}) |
||||
} |
); |
||||
) |
return; |
||||
); |
} |
||||
return; |
|
||||
} |
// If we navigate to a page
|
||||
|
if ( |
||||
// If we navigate to a page
|
event.request.mode === 'navigate' || |
||||
if ( |
(event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html')) |
||||
event.request.mode === 'navigate' || |
) { |
||||
(event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html')) |
event.respondWith( |
||||
) { |
|
||||
event.respondWith( |
// Make the request
|
||||
|
fetch(event.request) |
||||
// Make the request
|
.then(function (response) { |
||||
fetch(event.request) |
// If it's the homepage or a node page
|
||||
.then(function(response) { |
if (requestUrl.pathname === '/' || requestUrl.pathname.startsWith('/node/')) { |
||||
|
// Clone the response and read the html
|
||||
// If it's the homepage or a node page
|
response.clone().text().then(function (html) { |
||||
if(requestUrl.pathname === '/' || requestUrl.pathname.startsWith('/node/')) { |
// Modify the html so we know when it was cached
|
||||
|
html = html.replace('window.cacheDate=false;', 'window.cacheDate="' + Date() + '";'); |
||||
// Clone the response and read the html
|
var modifiedResponse = new Response(new Blob([html]), {headers: response.headers}); |
||||
response.clone().text().then(function(html) { |
|
||||
|
// Cache the modified response
|
||||
// Modify the html so we know when it was cached
|
caches.open(cacheName).then(function (cache) { |
||||
html = html.replace('window.cacheDate=false;', 'window.cacheDate="'+Date()+'";'); |
cache.put(event.request, modifiedResponse); |
||||
var modifiedResponse = new Response(new Blob([html]), { headers: response.headers }); |
}); |
||||
|
}); |
||||
// Cache the modified response
|
} |
||||
caches.open(cacheName).then(function(cache) { |
|
||||
cache.put(event.request, modifiedResponse); |
// Always return the original response
|
||||
}); |
return response; |
||||
}); |
}) |
||||
} |
|
||||
|
// If it fails
|
||||
// Always return the original response
|
.catch(function () { |
||||
return response; |
// Try and return a previously cached version
|
||||
}) |
return caches.match(event.request) |
||||
|
.then(function (response) { |
||||
// If it fails
|
// If we don't have a cached version show pretty offline page
|
||||
.catch(function() { |
return response || caches.match(offlineUrl); |
||||
|
}); |
||||
// Try and return a previously cached version
|
}) |
||||
return caches.match(event.request) |
); |
||||
.then(function(response) { |
} |
||||
|
|
||||
// If we don't have a cached version show pretty offline page
|
|
||||
return response || caches.match(offlineUrl); |
|
||||
}); |
|
||||
}) |
|
||||
); |
|
||||
} |
|
||||
}); |
}); |
||||
|
Loading…
Reference in new issue