diff --git a/package.json b/package.json
index 3279d35..e62b2c7 100644
--- a/package.json
+++ b/package.json
@@ -24,22 +24,24 @@
"main": "server.js",
"dependencies": {
"ejs": "^2.4.1",
+ "es6-promise": "^3.2.1",
+ "eslint-config-jonnybuchanan": "2.0.3",
"events": "1.1.0",
"express": "^4.13.4",
"firebase": "2.4.2",
"history": "2.1.1",
"isomorphic-fetch": "^2.2.1",
+ "nwb": "0.8.1",
"react": "15.0.2",
"react-dom": "15.0.2",
+ "react-resolver": "^3.0.1",
"react-router": "2.4.0",
"react-timeago": "3.0.0",
"reactfire": "0.7.0",
"scroll-behavior": "0.5.0",
"setimmediate": "1.0.4",
- "url-parse": "^1.1.1",
- "eslint-config-jonnybuchanan": "2.0.3",
- "nwb": "0.8.1",
"sw-precache": "^3.1.1",
- "sw-toolbox": "^3.1.1"
+ "sw-toolbox": "^3.1.1",
+ "url-parse": "^1.1.1"
}
}
diff --git a/public/service-worker.js b/public/service-worker.js
index 212d4fb..0e51c13 100644
--- a/public/service-worker.js
+++ b/public/service-worker.js
@@ -1,2 +1,249 @@
-// This file is intentionally without code.
-// It's present so that service worker registration will work when serving from the 'public' directory.
\ No newline at end of file
+/**
+ * Copyright 2015 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This generated service worker JavaScript will precache your site's resources.
+// The code needs to be saved in a .js file at the top-level of your site, and registered
+// from your pages in order to be used. See
+// https://github.com/googlechrome/sw-precache/blob/master/demo/app/js/service-worker-registration.js
+// for an example of how you can register this script and handle various service worker events.
+
+/* eslint-env worker, serviceworker */
+/* eslint-disable indent, no-unused-vars, no-multiple-empty-lines, max-nested-callbacks, space-before-function-paren */
+'use strict';
+
+
+
+
+importScripts("sw-toolbox.js","runtime-caching.js");
+
+
+/* eslint-disable quotes, comma-spacing */
+var PrecacheConfig = [["build/app.js","313d71d0a78527fb7beef3c3ff0138eb"],["build/app.js.map","44e5c1202bd9dc38796ebd8cb1c14040"],["build/vendor.js","f7535c3346e07c644907b4c1fec47a1b"],["css/style.css","ebd9bc483b1656c536c56c3035432bdd"],["img/android-chrome-144x144.png","31f44c8f8845e41196b389f2fdae392d"],["img/android-chrome-192x192.png","7c3470aa18f85e4454a35ef3ff8b8f6e"],["img/android-chrome-36x36.png","fc5a14316848badbd501e198f8607088"],["img/android-chrome-48x48.png","f8576bca4be18a4367e4847a09fc6945"],["img/android-chrome-72x72.png","6b26a8a135b07174d298489cc010083a"],["img/android-chrome-96x96.png","04cda150a70eb58221af3fdd4f7d4a6f"],["img/apple-touch-icon-114x114.png","e18affb685f0457672283a88c04084c9"],["img/apple-touch-icon-120x120.png","cd14469c7457cfc6d3aaf15d34faeddf"],["img/apple-touch-icon-144x144.png","95a8cb7d006c59252dd68ba73d31632a"],["img/apple-touch-icon-152x152.png","15dd03590ff7289c09cf10027597e699"],["img/apple-touch-icon-180x180.png","0b101591e8e263c6bff9133c7772194a"],["img/apple-touch-icon-57x57.png","628a477075d84a8d0996392aa6dec37c"],["img/apple-touch-icon-60x60.png","6b9fe001bc9e35320f9bb4eb28b1e6f1"],["img/apple-touch-icon-72x72.png","5830f2a4f9249b3bc3998481cc00825d"],["img/apple-touch-icon-76x76.png","812e9eb119b6bdd8f465a2d1118465b9"],["img/apple-touch-icon-precomposed.png","e45a9a06a4a9b850e3089c4e6e3ebc8d"],["img/apple-touch-icon.png","0b101591e8e263c6bff9133c7772194a"],["img/browserconfig.xml","f337354b6f80663075e7b32058c65149"],["img/favicon-16x16.png","9d784dc3f4da5477156423f5f106c1c6"],["img/favicon-32x32.png","21ea2cf9cd43cdc1f808cca76a1f6fa4"],["img/favicon-96x96.png","11e36fff4c95b572ffaeef9a848da568"],["img/favicon.ico","eaa33e22fc5dab05262d316b59160a45"],["img/logo.png","930a492dadf1ccb881bd91d424c8bf9e"],["img/mstile-144x144.png","3e9a3c273f9ac3b7a158132445534860"],["img/mstile-150x150.png","b0af3ec429e6828dc0606d8bb8e1421f"],["img/mstile-310x150.png","499b08d0d170e6ed89491d7e9691a8e8"],["img/mstile-310x310.png","625111493ee72a39db1420c9c235dfb3"],["img/mstile-70x70.png","4cdf64d2b55d8116c4ce8dd361a95772"],["img/safari-pinned-tab.svg","9bfe87bb482c5d6facab0d0084ce1e80"],["img/splashscreen-icon-384x384.png","e3080842f30a9137e1464f01ffb97e71"],["index-static.html","6b98cb5ee877097cadbd77807342c96c"],["manifest.json","3c0f2d46b398124d8358a69be42ee0c2"],["runtime-caching.js","87003e567d298b1b58cf2f57b4fb0ee2"],["service-worker.js","432e0ae0b3a06471d0c62c6844b88d07"],["sw-toolbox.js","42dd9073ba0a0c8e0ae2230432870678"]];
+/* eslint-enable quotes, comma-spacing */
+var CacheNamePrefix = 'sw-precache-v1-sw-precache-' + (self.registration ? self.registration.scope : '') + '-';
+
+
+var IgnoreUrlParametersMatching = [/^utm_/];
+
+
+
+var addDirectoryIndex = function (originalUrl, index) {
+ var url = new URL(originalUrl);
+ if (url.pathname.slice(-1) === '/') {
+ url.pathname += index;
+ }
+ return url.toString();
+ };
+
+var getCacheBustedUrl = function (url, now) {
+ now = now || Date.now();
+
+ var urlWithCacheBusting = new URL(url);
+ urlWithCacheBusting.search += (urlWithCacheBusting.search ? '&' : '') +
+ 'sw-precache=' + now;
+
+ return urlWithCacheBusting.toString();
+ };
+
+var isPathWhitelisted = function (whitelist, absoluteUrlString) {
+ // If the whitelist is empty, then consider all URLs to be whitelisted.
+ if (whitelist.length === 0) {
+ return true;
+ }
+
+ // Otherwise compare each path regex to the path of the URL passed in.
+ var path = (new URL(absoluteUrlString)).pathname;
+ return whitelist.some(function(whitelistedPathRegex) {
+ return path.match(whitelistedPathRegex);
+ });
+ };
+
+var populateCurrentCacheNames = function (precacheConfig,
+ cacheNamePrefix, baseUrl) {
+ var absoluteUrlToCacheName = {};
+ var currentCacheNamesToAbsoluteUrl = {};
+
+ precacheConfig.forEach(function(cacheOption) {
+ var absoluteUrl = new URL(cacheOption[0], baseUrl).toString();
+ var cacheName = cacheNamePrefix + absoluteUrl + '-' + cacheOption[1];
+ currentCacheNamesToAbsoluteUrl[cacheName] = absoluteUrl;
+ absoluteUrlToCacheName[absoluteUrl] = cacheName;
+ });
+
+ return {
+ absoluteUrlToCacheName: absoluteUrlToCacheName,
+ currentCacheNamesToAbsoluteUrl: currentCacheNamesToAbsoluteUrl
+ };
+ };
+
+var stripIgnoredUrlParameters = function (originalUrl,
+ ignoreUrlParametersMatching) {
+ var url = new URL(originalUrl);
+
+ url.search = url.search.slice(1) // Exclude initial '?'
+ .split('&') // Split into an array of 'key=value' strings
+ .map(function(kv) {
+ return kv.split('='); // Split each 'key=value' string into a [key, value] array
+ })
+ .filter(function(kv) {
+ return ignoreUrlParametersMatching.every(function(ignoredRegex) {
+ return !ignoredRegex.test(kv[0]); // Return true iff the key doesn't match any of the regexes.
+ });
+ })
+ .map(function(kv) {
+ return kv.join('='); // Join each [key, value] array into a 'key=value' string
+ })
+ .join('&'); // Join the array of 'key=value' strings into a string with '&' in between each
+
+ return url.toString();
+ };
+
+
+var mappings = populateCurrentCacheNames(PrecacheConfig, CacheNamePrefix, self.location);
+var AbsoluteUrlToCacheName = mappings.absoluteUrlToCacheName;
+var CurrentCacheNamesToAbsoluteUrl = mappings.currentCacheNamesToAbsoluteUrl;
+
+function deleteAllCaches() {
+ return caches.keys().then(function(cacheNames) {
+ return Promise.all(
+ cacheNames.map(function(cacheName) {
+ return caches.delete(cacheName);
+ })
+ );
+ });
+}
+
+self.addEventListener('install', function(event) {
+ var now = Date.now();
+
+ event.waitUntil(
+ caches.keys().then(function(allCacheNames) {
+ return Promise.all(
+ Object.keys(CurrentCacheNamesToAbsoluteUrl).filter(function(cacheName) {
+ return allCacheNames.indexOf(cacheName) === -1;
+ }).map(function(cacheName) {
+ var urlWithCacheBusting = getCacheBustedUrl(CurrentCacheNamesToAbsoluteUrl[cacheName],
+ now);
+
+ return caches.open(cacheName).then(function(cache) {
+ var request = new Request(urlWithCacheBusting, {credentials: 'same-origin'});
+ return fetch(request).then(function(response) {
+ if (response.ok) {
+ return cache.put(CurrentCacheNamesToAbsoluteUrl[cacheName], response);
+ }
+
+ console.error('Request for %s returned a response with status %d, so not attempting to cache it.',
+ urlWithCacheBusting, response.status);
+ // Get rid of the empty cache if we can't add a successful response to it.
+ return caches.delete(cacheName);
+ });
+ });
+ })
+ ).then(function() {
+ return Promise.all(
+ allCacheNames.filter(function(cacheName) {
+ return cacheName.indexOf(CacheNamePrefix) === 0 &&
+ !(cacheName in CurrentCacheNamesToAbsoluteUrl);
+ }).map(function(cacheName) {
+ return caches.delete(cacheName);
+ })
+ );
+ });
+ }).then(function() {
+ if (typeof self.skipWaiting === 'function') {
+ // Force the SW to transition from installing -> active state
+ self.skipWaiting();
+ }
+ })
+ );
+});
+
+if (self.clients && (typeof self.clients.claim === 'function')) {
+ self.addEventListener('activate', function(event) {
+ event.waitUntil(self.clients.claim());
+ });
+}
+
+self.addEventListener('message', function(event) {
+ if (event.data.command === 'delete_all') {
+ console.log('About to delete all caches...');
+ deleteAllCaches().then(function() {
+ console.log('Caches deleted.');
+ event.ports[0].postMessage({
+ error: null
+ });
+ }).catch(function(error) {
+ console.log('Caches not deleted:', error);
+ event.ports[0].postMessage({
+ error: error
+ });
+ });
+ }
+});
+
+
+self.addEventListener('fetch', function(event) {
+ if (event.request.method === 'GET') {
+ var urlWithoutIgnoredParameters = stripIgnoredUrlParameters(event.request.url,
+ IgnoreUrlParametersMatching);
+
+ var cacheName = AbsoluteUrlToCacheName[urlWithoutIgnoredParameters];
+ var directoryIndex = 'index.html';
+ if (!cacheName && directoryIndex) {
+ urlWithoutIgnoredParameters = addDirectoryIndex(urlWithoutIgnoredParameters, directoryIndex);
+ cacheName = AbsoluteUrlToCacheName[urlWithoutIgnoredParameters];
+ }
+
+ var navigateFallback = '';
+ // Ideally, this would check for event.request.mode === 'navigate', but that is not widely
+ // supported yet:
+ // https://code.google.com/p/chromium/issues/detail?id=540967
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1209081
+ if (!cacheName && navigateFallback && event.request.headers.has('accept') &&
+ event.request.headers.get('accept').includes('text/html') &&
+ /* eslint-disable quotes, comma-spacing */
+ isPathWhitelisted([], event.request.url)) {
+ /* eslint-enable quotes, comma-spacing */
+ var navigateFallbackUrl = new URL(navigateFallback, self.location);
+ cacheName = AbsoluteUrlToCacheName[navigateFallbackUrl.toString()];
+ }
+
+ if (cacheName) {
+ event.respondWith(
+ // Rely on the fact that each cache we manage should only have one entry, and return that.
+ caches.open(cacheName).then(function(cache) {
+ return cache.keys().then(function(keys) {
+ return cache.match(keys[0]).then(function(response) {
+ if (response) {
+ return response;
+ }
+ // If for some reason the response was deleted from the cache,
+ // raise and exception and fall back to the fetch() triggered in the catch().
+ throw Error('The cache ' + cacheName + ' is empty.');
+ });
+ });
+ }).catch(function(e) {
+ console.warn('Couldn\'t serve response for "%s" from cache: %O', event.request.url, e);
+ return fetch(event.request);
+ })
+ );
+ }
+ }
+});
+
+
+
+
diff --git a/server.js b/server.js
index dda4625..0664015 100644
--- a/server.js
+++ b/server.js
@@ -2,6 +2,8 @@ var express = require('express')
var React = require('react')
var renderToString = require('react-dom/server').renderToString
var ReactRouter = require('react-router')
+var Resolver = require('react-resolver').Resolver
+var RouterContext = React.createFactory(ReactRouter.RouterContext)
require('babel/register')
var routes = require('./src/routes')
@@ -16,18 +18,27 @@ app.get('*', function(req, res) {
ReactRouter.match({
routes: routes,
location: req.url
- }, function(err, redirectLocation, props) {
+ }, function(err, redirectLocation, renderProps) {
if (err) {
res.status(500).send(err.message)
}
else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
}
- else if (props) {
- var markup = renderToString(
- React.createElement(ReactRouter.RouterContext, props, null)
- )
- res.render('index', { markup: markup })
+ else if (renderProps) {
+ // https://github.com/allenkim67/isomorphic-demo/blob/2be59306196e84f66041dc7a03bbd0c805d371aa/server.js
+ Resolver
+ .resolve(function() {return RouterContext(renderProps)})
+ .then(function(resolverRes) {
+ console.log(resolverRes.data)
+ var markup = renderToString(
+ React.createElement(resolverRes.Resolved, renderProps, null)
+ )
+ res.render('index', {
+ markup: markup,
+ scriptTag: ''
+ })
+ })
}
else {
res.sendStatus(404)
diff --git a/src/Comment.js b/src/Comment.js
index db91788..77e843b 100644
--- a/src/Comment.js
+++ b/src/Comment.js
@@ -2,13 +2,14 @@ var React = require('react')
var ReactFireMixin = require('reactfire')
var CommentThreadStore = require('./stores/CommentThreadStore')
-var HNService = require('./services/HNService')
+// var HNService = require('./services/HNService')
var HNServiceRest = require('./services/HNServiceRest')
var SettingsStore = require('./stores/SettingsStore')
var CommentMixin = require('./mixins/CommentMixin')
var cx = require('./utils/buildClassName')
+var resolve = require('react-resolver').resolve
/**
* A comment in a thread.
@@ -86,21 +87,22 @@ var Comment = React.createClass({
}
},
- bindFirebaseRef() {
- if (SettingsStore.offlineMode) {
- HNServiceRest.itemRef(this.props.id).then(function(res) {
- return res.json()
- }).then(function(snapshot) {
- this.replaceState({ comment: snapshot })
- }.bind(this))
- }
- else {
- this.bindAsObject(HNService.itemRef(this.props.id), 'comment', this.handleFirebaseRefCancelled)
- }
-
- if (this.timeout) {
- this.timeout = null
- }
+ bindFirebaseRef(props) {
+ console.log('bindFirebaseRef', props)
+ // if (SettingsStore.offlineMode) {
+ // HNServiceRest.itemRef(props.id).then(function(res) {
+ // return res.json()
+ // }).then(function(snapshot) {
+ // this.replaceState({ comment: snapshot })
+ // }.bind(this))
+ // }
+ // else {
+ // this.bindAsObject(HNService.itemRef(props.id), 'comment', this.handleFirebaseRefCancelled)
+ // }
+
+ // if (this.timeout) {
+ // this.timeout = null
+ // }
},
/**
@@ -186,4 +188,18 @@ var Comment = React.createClass({
}
})
-module.exports = Comment
+/*
+What I'm attempting to do here is resolve comment so that we
+go through react-resolver anytime we need that data instead of
+directly through bindFirebaseRef per the resolver examples I have
+seen. Data seems to get returned client-side, but nothing correctly
+when doing the server-side render.
+ */
+module.exports = resolve('comment', function(props) {
+ return HNServiceRest.itemRef(props.id).then(function(res) {
+ return res.json()
+ }).then(function(snapshot) {
+ console.log('Comment snapshot:', snapshot)
+ return snapshot
+ })
+})(Comment)
diff --git a/src/index.js b/src/index.js
index 0812b31..4d67a8a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,7 +2,7 @@ require('setimmediate')
var createHashHistory = require('history/lib/createHashHistory')
var React = require('react')
-var {render} = require('react-dom')
+// var {render} = require('react-dom')
var Router = require('react-router/lib/Router')
var useRouterHistory = require('react-router/lib/useRouterHistory')
var withScroll = require('scroll-behavior/lib/withStandardScroll')
@@ -11,4 +11,10 @@ var routes = require('./routes')
var history = withScroll(useRouterHistory(createHashHistory)())
-render(, document.getElementById('app'))
+var Resolver = require('react-resolver').Resolver
+
+Resolver.render(function() { return }, document.getElementById('app'))
+
+// Router.run(routes, Router.HistoryLocation, function(Handler) {
+// Resolver.render(function() { return }, document.getElementById('app'))
+// })
diff --git a/src/services/HNServiceRest.js b/src/services/HNServiceRest.js
index 701ce2a..0c02ebd 100644
--- a/src/services/HNServiceRest.js
+++ b/src/services/HNServiceRest.js
@@ -1,4 +1,5 @@
/*global fetch*/
+require('es6-promise').polyfill()
require('isomorphic-fetch')
/*
A version of HNService which concumes the Firebase REST
diff --git a/src/stores/SettingsStore.js b/src/stores/SettingsStore.js
index ee4c861..c800a1c 100644
--- a/src/stores/SettingsStore.js
+++ b/src/stores/SettingsStore.js
@@ -10,7 +10,7 @@ var SettingsStore = {
showDeleted: false,
titleFontSize: 18,
listSpacing: 16,
- offlineMode: false,
+ offlineMode: true, // Temporarily enabled by default for testing
load() {
var json = storage.get(STORAGE_KEY)
diff --git a/src/views/index.ejs b/src/views/index.ejs
index 202afd3..fbae3fc 100644
--- a/src/views/index.ejs
+++ b/src/views/index.ejs
@@ -66,5 +66,6 @@
console.log('service worker is not supported');
}
+ <%- scriptTag %>