diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000..5ec9cc2
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+web: node index.js
\ No newline at end of file
diff --git a/app.json b/app.json
new file mode 100644
index 0000000..e0b7371
--- /dev/null
+++ b/app.json
@@ -0,0 +1,7 @@
+{
+ "name": "React HN",
+ "description": "React-powered Hacker News client",
+ "repository": "https://github.com/insin/react-hn",
+ "keywords": ["react", "hacker news", "hn"],
+ "image": "heroku/nodejs"
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index c4bce2b..5d7409d 100644
--- a/package.json
+++ b/package.json
@@ -12,11 +12,18 @@
"build": "npm run lint && cp node_modules/sw-toolbox/sw-toolbox.js public/sw-toolbox.js && nwb build && npm run precache",
"lint": "eslint src",
"lint:fix": "eslint --fix .",
- "start": "nwb serve",
+ "start": "node src/server.js",
+ "serve": "nwb serve",
"precache": "sw-precache --root=public --config=sw-precache-config.json"
},
+ "engines": {
+ "node": "5.9.1"
+ },
+ "main": "src/server.js",
"dependencies": {
+ "ejs": "^2.4.1",
"events": "1.1.0",
+ "express": "^4.13.4",
"firebase": "2.4.2",
"firetruck.js": "0.1.1",
"history": "2.1.1",
@@ -26,7 +33,8 @@
"react-timeago": "3.0.0",
"reactfire": "0.7.0",
"scroll-behavior": "0.5.0",
- "setimmediate": "1.0.4"
+ "setimmediate": "1.0.4",
+ "url-parse": "^1.1.1"
},
"devDependencies": {
"eslint-config-jonnybuchanan": "2.0.3",
diff --git a/public/index.html b/public/index-static.html
similarity index 100%
rename from public/index.html
rename to public/index-static.html
diff --git a/src/App.js b/src/App.js
index af407da..035e80d 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,4 +1,3 @@
-/* global __VERSION__ */
var React = require('react')
var Link = require('react-router/lib/Link')
@@ -19,10 +18,12 @@ var App = React.createClass({
SettingsStore.load()
StoryStore.loadSession()
UpdatesStore.loadSession()
+ if (typeof window === 'undefined') return
window.addEventListener('beforeunload', this.handleBeforeUnload)
},
componentWillUnmount() {
+ if (typeof window === 'undefined') return
window.removeEventListener('beforeunload', this.handleBeforeUnload)
},
@@ -60,7 +61,6 @@ var App = React.createClass({
{this.props.children}
diff --git a/src/Stories.js b/src/Stories.js
index 8dc8fda..c60d523 100644
--- a/src/Stories.js
+++ b/src/Stories.js
@@ -35,7 +35,7 @@ var Stories = React.createClass({
}
},
- componentWillMount() {
+ componentDidMount() {
setTitle(this.props.title)
this.store = new StoryStore(this.props.type)
this.store.addListener('update', this.handleUpdate)
diff --git a/src/mixins/ItemMixin.js b/src/mixins/ItemMixin.js
index a4fcd6b..6712ade 100644
--- a/src/mixins/ItemMixin.js
+++ b/src/mixins/ItemMixin.js
@@ -4,18 +4,16 @@ var TimeAgo = require('react-timeago').default
var SettingsStore = require('../stores/SettingsStore')
var pluralise = require('../utils/pluralise')
+var urlParse = require('url-parse')
-var parseHost = (function() {
- var a = document.createElement('a')
- return function(url) {
- a.href = url
- var parts = a.hostname.split('.').slice(-3)
- if (parts[0] === 'www') {
- parts.shift()
- }
- return parts.join('.')
+var parseHost = function(url) {
+ var hostname = (urlParse(url, true)).hostname
+ var parts = hostname.split('.').slice(-3)
+ if (parts[0] === 'www') {
+ parts.shift()
}
-})()
+ return parts.join('.')
+}
/**
* Reusable logic for displaying an item.
diff --git a/src/server.js b/src/server.js
new file mode 100644
index 0000000..7a775d5
--- /dev/null
+++ b/src/server.js
@@ -0,0 +1,44 @@
+var express = require('express')
+var React = require('react')
+var renderToString = require('react-dom/server').renderToString
+var ReactRouter = require('react-router')
+
+require('babel/register')
+var routes = require('./routes')
+
+var app = express()
+app.set('view engine', 'ejs')
+app.set('views', process.cwd() + '/src/views')
+app.set('port', (process.env.PORT || 5000))
+app.use(express.static('public'))
+
+app.get('*', function(req, res) {
+ ReactRouter.match({
+ routes: routes,
+ location: req.url
+ }, function(err, redirectLocation, props) {
+ 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 {
+ res.sendStatus(404)
+ }
+ })
+})
+
+app.listen(app.get('port'), function(err) {
+ if (err) {
+ console.log(err)
+ return
+ }
+ console.log('Running app at localhost:' + app.get('port'))
+})
diff --git a/src/services/HNService.js b/src/services/HNService.js
index c8e7124..6b9b626 100644
--- a/src/services/HNService.js
+++ b/src/services/HNService.js
@@ -1,7 +1,6 @@
-var Firetruck = require('firetruck.js')
+var Firebase = require('firebase')
-var api = new Firetruck('https://hacker-news.firebaseio.com/v0')
-api.restore()
+var api = new Firebase('https://hacker-news.firebaseio.com/v0')
function fetchItem(id, cb) {
itemRef(id).once('value', function(snapshot) {
diff --git a/src/stores/StoryStore.js b/src/stores/StoryStore.js
index 34057df..9400a69 100644
--- a/src/stores/StoryStore.js
+++ b/src/stores/StoryStore.js
@@ -99,6 +99,7 @@ class StoryStore extends EventEmitter {
start() {
firebaseRef = HNService.storiesRef(this.type)
firebaseRef.on('value', this.onStoriesUpdated)
+ if (typeof window === 'undefined') return
window.addEventListener('storage', this.onStorage)
}
@@ -107,6 +108,7 @@ class StoryStore extends EventEmitter {
firebaseRef.off()
firebaseRef = null
}
+ if (typeof window === 'undefined') return
window.removeEventListener('storage', this.onStorage)
}
}
@@ -124,6 +126,7 @@ extend(StoryStore, {
* Deserialise caches from sessionStorage.
*/
loadSession() {
+ if (typeof window === 'undefined') return
idCache = parseJSON(window.sessionStorage.idCache, {})
itemCache = parseJSON(window.sessionStorage.itemCache, {})
},
@@ -132,6 +135,7 @@ extend(StoryStore, {
* Serialise caches to sessionStorage as JSON.
*/
saveSession() {
+ if (typeof window === 'undefined') return
window.sessionStorage.idCache = JSON.stringify(idCache)
window.sessionStorage.itemCache = JSON.stringify(itemCache)
}
diff --git a/src/stores/UpdatesStore.js b/src/stores/UpdatesStore.js
index ff711e6..9e6029a 100644
--- a/src/stores/UpdatesStore.js
+++ b/src/stores/UpdatesStore.js
@@ -100,12 +100,14 @@ function handleUpdateItems(items) {
var UpdatesStore = extend(new EventEmitter(), {
loadSession() {
+ if (typeof window === 'undefined') return
var json = window.sessionStorage.updates
updatesCache = (json ? JSON.parse(json) : {comments: {}, stories: {}})
populateUpdates()
},
saveSession() {
+ if (typeof window === 'undefined') return
window.sessionStorage.updates = JSON.stringify(updatesCache)
},
diff --git a/src/utils/setTitle.js b/src/utils/setTitle.js
index e432fe5..34e2faf 100644
--- a/src/utils/setTitle.js
+++ b/src/utils/setTitle.js
@@ -1,6 +1,7 @@
var {SITE_TITLE} = require('./constants')
function setTitle(title) {
+ if (typeof document === 'undefined') return
document.title = (title ? title + ' | ' + SITE_TITLE : SITE_TITLE)
}
diff --git a/src/utils/storage.js b/src/utils/storage.js
index f64187c..d077b8b 100644
--- a/src/utils/storage.js
+++ b/src/utils/storage.js
@@ -1,9 +1,14 @@
module.exports = {
get(key, defaultValue) {
- var value = window.localStorage[key]
- return (typeof value != 'undefined' ? value : defaultValue)
+ if (typeof window !== 'undefined') {
+ var value = window.localStorage[key]
+ return (typeof value != 'undefined' ? value : defaultValue)
+ }
},
set(key, value) {
- window.localStorage[key] = value
+ if (typeof window !== 'undefined') {
+ window.localStorage[key] = value
+ }
}
}
+
diff --git a/src/views/index.ejs b/src/views/index.ejs
new file mode 100644
index 0000000..202afd3
--- /dev/null
+++ b/src/views/index.ejs
@@ -0,0 +1,70 @@
+
+
+
+
+
+ React HN
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <%- markup %>
+
+
+
+
+
\ No newline at end of file