├── src
├── shared.styl
├── filters
│ ├── domain.js
│ └── from-now.js
├── index.js
├── routes.js
├── components
│ ├── comment.vue
│ ├── user-view.vue
│ ├── news-view.vue
│ ├── item.vue
│ └── item-view.vue
├── store.js
└── app.vue
├── .gitignore
├── logo.png
├── README.md
├── index.html
├── webpack.config.js
├── package.json
└── LICENSE
/src/shared.styl:
--------------------------------------------------------------------------------
1 | $bg = #f6f6ef
2 | $gray = #828282
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | *.swp
4 | *~
5 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kazupon/vue-router-hackernews/HEAD/logo.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HackerNews clone with vue.js + vue-router
2 |
3 | Alter [Vue.js Hackernews clone](https://github.com/vuejs/vue-hackernews).
4 |
5 | # LICENSE
6 | MIT
7 |
--------------------------------------------------------------------------------
/src/filters/domain.js:
--------------------------------------------------------------------------------
1 | let parser = document.createElement('a')
2 |
3 | export default function (url) {
4 | parser.href = url
5 | return parser.hostname
6 | }
7 |
--------------------------------------------------------------------------------
/src/filters/from-now.js:
--------------------------------------------------------------------------------
1 | export default function (time) {
2 | let between = Date.now() / 1000 - Number(time)
3 | if (between < 3600) {
4 | return ~~(between / 60) + ' minutes'
5 | } else if (between < 86400) {
6 | return ~~(between / 3600) + ' hours'
7 | } else {
8 | return ~~(between / 86400) + ' days'
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | HackerNews Clone
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Boot up the Vue instance and wire up the router.
3 | */
4 |
5 | import Vue from 'vue'
6 | import VueRouter from 'vue-router'
7 | import route from './routes'
8 |
9 | Vue.use(VueRouter)
10 |
11 | const router = new VueRouter({
12 | hashbang: true,
13 | history: false
14 | })
15 | route(router)
16 |
17 |
18 | const App = Vue.extend(require('./app.vue'))
19 |
20 | router.start(App, '#app')
21 |
--------------------------------------------------------------------------------
/src/routes.js:
--------------------------------------------------------------------------------
1 | export default function (router) {
2 | router.map({
3 | '/news/:page': {
4 | component: require('./components/news-view.vue')
5 | },
6 | '/user/:id': {
7 | component: require('./components/user-view.vue')
8 | },
9 | '/item/:id': {
10 | component: require('./components/item-view.vue')
11 | }
12 | })
13 |
14 | router.redirect({
15 | '*': '/news/1'
16 | })
17 | }
18 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var vue = require('vue-loader')
2 |
3 | module.exports = {
4 | entry: './src/index.js',
5 | output: {
6 | path: './build',
7 | filename: 'build.js'
8 | },
9 | module: {
10 | loaders: [{
11 | test: /\.vue$/,
12 | loader: vue.withLoaders({
13 | js: 'babel?optional[]=runtime'
14 | })
15 | }, {
16 | test: /\.js$/,
17 | exclude: /node_modules|vue\/src/,
18 | loader: 'babel?optional[]=runtime'
19 | }]
20 | },
21 | devtool: 'source-map'
22 | }
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-router-hackernews",
3 | "description": "Hackernews clone with vue.js + vue-router",
4 | "version": "1.0.0",
5 | "author": "kazuya kawaguchi",
6 | "bugs": {
7 | "url": "https://github.com/kazupon/vue-router-hackernews/issues"
8 | },
9 | "homepage": "https://github.com/kazupon/vue-router-hackernews#README",
10 | "license": "MIT",
11 | "main": "index.js",
12 | "private": true,
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/kazupon/vue-router-hackernews.git"
16 | },
17 | "scripts": {
18 | "dev": "webpack --watch",
19 | "build": "webpack"
20 | },
21 | "devDependencies": {
22 | "babel-core": "^5.8.22",
23 | "babel-loader": "^5.3.2",
24 | "babel-runtime": "^5.8.20",
25 | "css-loader": "^0.16.0",
26 | "firebase": "^2.2.9",
27 | "html-loader": "^0.3.0",
28 | "style-loader": "^0.12.3",
29 | "stylus-loader": "^1.2.1",
30 | "vue": "^0.12.10",
31 | "vue-loader": "^3.0.3",
32 | "vue-router": "^0.5.1",
33 | "webpack": "^1.11.0"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 kazuya kawaguchi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/src/components/comment.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
35 |
36 |
37 |
59 |
--------------------------------------------------------------------------------
/src/components/user-view.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - user: {{user.id}}
5 | - created: {{user.created | fromNow}} ago
6 | - karma: {{user.karma}}
7 | -
8 | about:
9 |
10 |
11 |
12 |
13 | submissions
14 | comments
15 |
16 |
17 |
18 |
19 |
20 |
47 |
48 |
49 |
64 |
--------------------------------------------------------------------------------
/src/components/news-view.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
14 |
54 |
55 |
56 |
74 |
--------------------------------------------------------------------------------
/src/components/item.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{index}}.
4 |
5 | {{item.title}}
6 | {{item.title}}
7 |
8 | ({{item.url | domain}})
9 |
10 |
11 |
12 |
13 | {{item.score}} points by
14 | {{item.by}}
15 |
16 | {{item.time | fromNow}} ago
17 |
20 |
21 |
22 |
23 |
24 |
25 |
50 |
51 |
52 |
78 |
--------------------------------------------------------------------------------
/src/components/item-view.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | -
6 |
{{text}}
7 | {{score}} points
8 |
9 |
10 |
13 |
No comments yet.
14 |
15 |
16 |
17 |
18 |
65 |
66 |
67 |
87 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import Firebase from 'firebase'
2 | import { EventEmitter as Emitter } from 'events'
3 |
4 | let api = new Firebase('https://hacker-news.firebaseio.com/v0')
5 | let storiesPerPage = 30
6 | let cachedStoryIds = []
7 | let cachedStories = {}
8 | let store = new Emitter()
9 |
10 | export default store
11 |
12 | /**
13 | * Subscribe to real time updates of the top 100 stories,
14 | * and cache the IDs locally.
15 | */
16 |
17 | api.child('topstories').on('value', (snapshot) => {
18 | cachedStoryIds = snapshot.val()
19 | store.emit('update')
20 | })
21 |
22 | /**
23 | * Fetch an item data with given id.
24 | *
25 | * @param {Number} id
26 | * @param {Function} cb(item)
27 | */
28 |
29 | store.fetchItem = function (id, cb) {
30 | if (cachedStories[id]) {
31 | cb(cachedStories[id])
32 | } else {
33 | api.child('item/' + id).once('value', (snapshot) => {
34 | let story = snapshot.val()
35 | cachedStories[id] = story
36 | cb(story)
37 | })
38 | }
39 | }
40 |
41 | /**
42 | * Fetch the given list of items.
43 | *
44 | * @param {Array} ids
45 | * @param {Function} cb(items)
46 | */
47 |
48 | store.fetchItems = function (ids, cb) {
49 | if (!ids || !ids.length) return cb([])
50 | let items = []
51 | ids.forEach((id) => {
52 | store.fetchItem(id, addItem)
53 | })
54 | function addItem (item) {
55 | items.push(item)
56 | if (items.length >= ids.length) {
57 | cb(items)
58 | }
59 | }
60 | }
61 |
62 | /**
63 | * Fetch items for the given page.
64 | *
65 | * @param {Number} page
66 | * @param {Function} cb(stories)
67 | */
68 |
69 | store.fetchItemsByPage = function (page, cb) {
70 | let start = (page - 1) * storiesPerPage
71 | let end = page * storiesPerPage
72 | let ids = cachedStoryIds.slice(start, end)
73 | store.fetchItems(ids, cb)
74 | }
75 |
76 | /**
77 | * Fetch a user data with given id.
78 | *
79 | * @param {Number} id
80 | * @param {Function} cb(user)
81 | */
82 |
83 | store.fetchUser = function (id, cb) {
84 | api.child('user/' + id).once('value', (snapshot) => {
85 | cb(snapshot.val())
86 | })
87 | }
88 |
--------------------------------------------------------------------------------
/src/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
17 |
18 |
26 |
27 |
28 |
98 |
--------------------------------------------------------------------------------