Browse Source

Follow xo style

pull/11/head
Luke Childs 8 years ago
parent
commit
3b568f7b2e
  1. 4
      controllers/about.js
  2. 9
      controllers/error.js
  3. 6
      controllers/error404.js
  4. 12
      controllers/index.js
  5. 57
      controllers/listing.js
  6. 4
      controllers/no-connection.js
  7. 54
      controllers/node.js
  8. 31
      index.js
  9. 80
      lib/bandwidth-chart.js
  10. 35
      lib/minify.js
  11. 92
      lib/nunjucks-filters.js
  12. 8
      lib/nunjucks-middleware.js
  13. 81
      lib/tor.js
  14. 513
      public/assets/enhancements.js
  15. 165
      public/sw.js

4
controllers/about.js

@ -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'
}); });

9
controllers/error.js

@ -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});
}; };

6
controllers/error404.js

@ -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});
}; };

12
controllers/index.js

@ -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')
}; };

57
controllers/listing.js

@ -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);
});
}

4
controllers/no-connection.js

@ -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'
}); });

54
controllers/node.js

@ -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);
}); });
} };

31
index.js

@ -1,24 +1,25 @@
const express = require('express'); const express = require('express');
const anonlytics = require('anonlytics-express'); const anonlytics = require('anonlytics-express');
const nunjucks = require('nunjucks'); const nunjucks = require('nunjucks');
const nunjucksFilters = require('./lib/nunjucks-filters'); const compression = require('compression');
const nunjucksMiddleware = require('./lib/nunjucks-middleware'); const nunjucksFilters = require('./lib/nunjucks-filters');
const compression = require('compression'); const nunjucksMiddleware = require('./lib/nunjucks-middleware');
const minify = require('./lib/minify'); const minify = require('./lib/minify');
const controllers = require('./controllers'); const controllers = require('./controllers');
const app = express();
const port = process.env.port || 3000; const app = express();
const port = process.env.port || 3000;
// Trust proxy headers if we're deployed on now // Trust proxy headers if we're deployed on now
if(process.env.NOW) { if (process.env.NOW) {
app.enable('trust proxy'); app.enable('trust proxy');
} }
// Analytics // Analytics
app.use(anonlytics()); app.use(anonlytics());
// Setup nunjucks // Setup nunjucks
nunjucks.configure('views', { express: app }); nunjucks.configure('views', {express: app});
nunjucksFilters(app); nunjucksFilters(app);
app.use(nunjucksMiddleware); app.use(nunjucksMiddleware);
@ -35,8 +36,8 @@ app.get('/about', controllers.about);
app.get('/no-connection', controllers.noConnection); app.get('/no-connection', controllers.noConnection);
// Serve assets with cache headers // Serve assets with cache headers
app.use('/sw.js', express.static(`${__dirname}/public/sw.js`, { maxAge: '1 hour' })); app.use('/sw.js', express.static(`${__dirname}/public/sw.js`, {maxAge: '1 hour'}));
app.use(express.static(`${__dirname}/public`, { maxAge: '1 year' })); app.use(express.static(`${__dirname}/public`, {maxAge: '1 year'}));
// Errors // Errors
app.use(controllers.error404); app.use(controllers.error404);

80
lib/bandwidth-chart.js

@ -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 ''; });
} };
}

35
lib/minify.js

@ -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
} }
}) })
]; ];

92
lib/nunjucks-filters.js

@ -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]);
}); });

8
lib/nunjucks-middleware.js

@ -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();
} };

81
lib/tor.js

@ -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 []; }
} });
}); }
}
}; };

513
public/assets/enhancements.js

@ -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');
}
});
}
})(); })();

165
public/sw.js

@ -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…
Cancel
Save