├── src ├── ui │ ├── styles │ │ └── app.scss │ ├── components │ │ ├── inc │ │ │ └── helper.ts │ │ ├── concat │ │ │ └── helper.ts │ │ ├── active │ │ │ └── helper.ts │ │ ├── gt │ │ │ └── helper.ts │ │ ├── comments-route │ │ │ ├── component.ts │ │ │ ├── comment-item │ │ │ │ └── template.hbs │ │ │ └── template.hbs │ │ ├── time │ │ │ └── helper.ts │ │ ├── list-view │ │ │ └── template.hbs │ │ ├── about-route │ │ │ └── template.hbs │ │ └── glimmer-hn │ │ │ ├── template.hbs │ │ │ └── component.ts │ └── index.html ├── main.ts ├── utils │ └── router.ts └── index.ts ├── .gitignore ├── public ├── robots.txt ├── images │ └── glimmer-192x192.png └── manifest.json ├── server ├── .eslintrc.js ├── index.js └── mocks │ └── top.js ├── README.md ├── config ├── environment.js ├── module-map.d.ts └── resolver-configuration.d.ts ├── tsconfig.json ├── .editorconfig ├── ember-cli-build.js └── package.json /src/ui/styles/app.scss: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: #444; 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tmp 3 | dist 4 | baseline-dist 5 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | node: true 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /src/ui/components/inc/helper.ts: -------------------------------------------------------------------------------- 1 | export default function inc([num]) { 2 | return ++num; 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # glimmer-hn 2 | 3 | Hackernews demoware app. 4 | 5 | ``` 6 | yarn run start 7 | ``` 8 | -------------------------------------------------------------------------------- /src/ui/components/concat/helper.ts: -------------------------------------------------------------------------------- 1 | export default function concat(params) { 2 | return params.join(''); 3 | } -------------------------------------------------------------------------------- /public/images/glimmer-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadhietala/glimmer-hn/HEAD/public/images/glimmer-192x192.png -------------------------------------------------------------------------------- /src/ui/components/active/helper.ts: -------------------------------------------------------------------------------- 1 | export default function active([path]) { 2 | return path === location.hash ? 'active' : ''; 3 | } 4 | -------------------------------------------------------------------------------- /src/ui/components/gt/helper.ts: -------------------------------------------------------------------------------- 1 | export default function _gt([left, right, consequent, alternative]) { 2 | if (left > right) { 3 | return consequent; 4 | } 5 | 6 | return alternative; 7 | } -------------------------------------------------------------------------------- /src/ui/components/comments-route/component.ts: -------------------------------------------------------------------------------- 1 | import Component, { tracked } from "@glimmer/component"; 2 | 3 | export default class CommentsRoute extends Component { 4 | constructor() { 5 | super(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(environment) { 4 | let ENV = { 5 | modulePrefix: 'glimmer-hn', 6 | environment: environment 7 | }; 8 | 9 | return ENV; 10 | }; 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es2015", 5 | "inlineSourceMap": true, 6 | "inlineSources": true, 7 | "moduleResolution": "node", 8 | "experimentalDecorators": true 9 | }, 10 | "exclude": [ 11 | "node_modules", 12 | "tmp", 13 | "dist" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /config/module-map.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is just a placeholder file to keep TypeScript aware editors happy. At build time, 3 | * it will be replaced with a complete map of resolvable module paths => rolled up contents. 4 | */ 5 | 6 | export interface Dict { 7 | [index: string]: T; 8 | } 9 | 10 | declare let map: Dict; 11 | export default map; 12 | -------------------------------------------------------------------------------- /src/ui/components/comments-route/comment-item/template.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{@comment.user}} 5 | {{@comment.time_ago}} 6 |
7 |
8 |
9 | {{{@comment.content}}} 10 |
11 | 12 | {{yield @comment.comments}} 13 |
14 | -------------------------------------------------------------------------------- /config/resolver-configuration.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is just a placeholder file to keep TypeScript aware editors happy. At build time, 3 | * it will be replaced with a resolver configuration composed from your application's 4 | * `config/environment.js` (and supplemented with default settings as possible). 5 | */ 6 | 7 | import { ResolverConfiguration } from '@glimmer/resolver'; 8 | declare var _default: ResolverConfiguration; 9 | export default _default; 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.hbs] 17 | insert_final_newline = false 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /src/ui/components/time/helper.ts: -------------------------------------------------------------------------------- 1 | const nearest = (count, units) => { 2 | return count = ~~count, 1 !== count && (units += "s"), count + " " + units; 3 | }; 4 | 5 | export default function timeFormat([time]) { 6 | const delta = Date.now()/1000 - time; 7 | 8 | if (delta < 3600) { 9 | return nearest(delta/60, "minute"); 10 | } 11 | if (delta < 86400) { 12 | return nearest(delta/3600, "hour"); 13 | } 14 | return nearest(delta/86400, "day"); 15 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import Application from '@glimmer/application'; 2 | import Resolver, { ResolverConfiguration, BasicModuleRegistry } from '@glimmer/resolver'; 3 | import moduleMap from '../config/module-map'; 4 | import resolverConfiguration from '../config/resolver-configuration'; 5 | 6 | export default class App extends Application { 7 | constructor() { 8 | let moduleRegistry = new BasicModuleRegistry(moduleMap); 9 | let resolver = new Resolver(resolverConfiguration, moduleRegistry); 10 | 11 | super({ 12 | rootName: resolverConfiguration.app.rootName, 13 | resolver 14 | }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ui/components/list-view/template.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{~#each @results key="index" as |result i|~}} 3 | 12 | {{~/each~}} 13 |
14 | -------------------------------------------------------------------------------- /src/ui/components/about-route/template.hbs: -------------------------------------------------------------------------------- 1 |
2 |

About this site

3 | 4 |

HackerNews Progressive Web App Demoware written in Glimmer. Glimmer is one of the fastest DOM rendering engines, delivering exceptional performance for initial renders as well as updates. Architected like a virtual machine (VM), Glimmer compiles your templates into low-level code so it can run as fast as possible—without sacrificing ease of use.

5 | 6 |

Where other small component libraries leave you to figure out everything from directory structure to build tooling, Glimmer apps are built with Ember CLI, so you get a production-ready build pipeline in seconds. No fussy configuration files needed.

7 |
8 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | // To use it create some files under `mocks/` 5 | // e.g. `server/mocks/ember-hamsters.js` 6 | // 7 | // module.exports = function(app) { 8 | // app.get('/ember-hamsters', function(req, res) { 9 | // res.send('hello'); 10 | // }); 11 | // }; 12 | 13 | module.exports = function(app) { 14 | const globSync = require('glob').sync; 15 | const mocks = globSync('./mocks/**/*.js', { cwd: __dirname }).map(require); 16 | const proxies = globSync('./proxies/**/*.js', { cwd: __dirname }).map(require); 17 | 18 | // Log proxy requests 19 | const morgan = require('morgan'); 20 | app.use(morgan('dev')); 21 | 22 | mocks.forEach(route => route(app)); 23 | proxies.forEach(route => route(app)); 24 | }; 25 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Glimmer HN", 3 | "short_name": "Glimmer HN", 4 | "icons": [ 5 | { 6 | "src": "/images/glimmer-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/images/glimmer-192x192.png", 12 | "sizes": "256x256", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "/images/glimmer-192x192.png", 17 | "sizes": "384x384", 18 | "type": "image/png" 19 | }, 20 | { 21 | "src": "/images/glimmer-192x192.png", 22 | "sizes": "512x512", 23 | "type": "image/png" 24 | } 25 | ], 26 | "start_url": "./?utm_source=homescreen", 27 | "background_color": "#4CC1FC", 28 | "display": "standalone", 29 | "theme_color": "#222222" 30 | } 31 | -------------------------------------------------------------------------------- /src/ui/components/comments-route/template.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | {{@article.title}} 5 |

6 | ({{@article.domain}}) 7 |

{{@article.points}} points by {{@article.user}}

8 |
9 |
10 |

{{@article.comments_count}} comments

11 |
12 |
13 | {{#each @comments key="id" as |comment|}} 14 | 15 |
16 | {{#each nestedComment key="id" as |nested|}} 17 | 18 | {{/each}} 19 |
20 |
21 | {{/each}} 22 |
23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /src/ui/components/glimmer-hn/template.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{~#-in-element nav~}} 3 |
  • 4 | top 5 |
  • 6 |
  • 7 | new 8 |
  • 9 |
  • 10 | show 11 |
  • 12 |
  • 13 | ask 14 |
  • 15 |
  • 16 | jobs 17 |
  • 18 |
  • 19 | about 20 |
  • 21 | {{~/-in-element~}} 22 | 23 | {{#if isListView}} 24 | 25 | {{else if isComments}} 26 | 27 | {{else}} 28 | 29 | 30 | {{/if}} 31 | 32 |
    33 | -------------------------------------------------------------------------------- /src/utils/router.ts: -------------------------------------------------------------------------------- 1 | 2 | import RouteRecognizer from 'route-recognizer'; 3 | 4 | let router = new RouteRecognizer(); 5 | 6 | router.map(function(match) { 7 | match('/item/:articleId').to('comments'); 8 | match('/about').to('about'); 9 | match('/jobs').to('jobs'); 10 | match('/ask').to('ask'); 11 | match('/show').to('show'); 12 | match('/new').to('newest'); 13 | match('/').to('top'); 14 | }); 15 | 16 | export function getHash(location) { 17 | let href = location.href; 18 | let hashIndex = href.indexOf('#'); 19 | 20 | if (hashIndex === -1) { 21 | return '/'; 22 | } else { 23 | return href.substr(hashIndex + 1); 24 | } 25 | } 26 | 27 | let changeCallback, lastPath; 28 | function _hashchangeHandler() { 29 | let path = getHash(window.location); 30 | 31 | if (path === lastPath) { return; } 32 | 33 | lastPath = path; 34 | 35 | let matches = router.recognize(path); 36 | 37 | changeCallback(matches[0].handler, matches[0].params); 38 | } 39 | 40 | export function onChange(callback: Function) { 41 | changeCallback = callback; 42 | 43 | _hashchangeHandler(); // kick it off the first time 44 | window.addEventListener('hashchange', _hashchangeHandler); 45 | } 46 | 47 | export default router; 48 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import App from './main'; 2 | import { ComponentManager, setPropertyDidChange } from '@glimmer/component'; 3 | 4 | const app = new App(); 5 | const containerElement = document.getElementById('app'); 6 | 7 | containerElement.setAttribute('class', 'viewHasHeader'); 8 | 9 | setPropertyDidChange(() => { 10 | app.scheduleRerender(); 11 | }); 12 | 13 | app.registerInitializer({ 14 | initialize(registry) { 15 | registry.register(`component-manager:/${app.rootName}/component-managers/main`, ComponentManager) 16 | } 17 | }); 18 | 19 | app.renderComponent('glimmer-hn', containerElement, null); 20 | 21 | 22 | 23 | requestAnimationFrame(() => { 24 | performance.mark('beforeRender'); 25 | app.boot(); 26 | performance.mark('afterRender'); 27 | requestAnimationFrame(() => { 28 | performance.mark('afterPaint'); 29 | 30 | setTimeout(() => { 31 | if (location.search === '?perf.tracing') { 32 | document.location.href = 'about:blank'; 33 | } else { 34 | performance.measure('download-parse-compile', 'beforeApp', 'afterApp'); 35 | performance.measure('render', 'beforeRender', 'afterRender'); 36 | performance.measure('paint', 'afterRender', 'afterPaint'); 37 | } 38 | }, 100); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const GlimmerApp = require('@glimmer/application-pipeline').GlimmerApp; 4 | 5 | module.exports = function(defaults) { 6 | let app = new GlimmerApp(defaults, { 7 | minifyJS: { 8 | enabled: process.env.EMBER_ENV === 'production', 9 | options: { 10 | mangle: true, 11 | compress: { 12 | // disable these have performance issues 13 | negate_iife: false, 14 | }, 15 | output: { 16 | semicolons: false, 17 | }, 18 | }, 19 | }, 20 | fingerprint: { 21 | exclude: ['images/glimmer-192x192'], 22 | } 23 | }); 24 | 25 | // Use `app.import` to add additional libraries to the generated 26 | // output files. 27 | // 28 | // If you need to use different assets in different 29 | // environments, specify an object as the first parameter. That 30 | // object's keys should be the environment name and the values 31 | // should be the asset to use in that environment. 32 | // 33 | // If the library that you are including contains AMD or ES6 34 | // modules that you would like to import into your application 35 | // please specify an object with the list of modules as keys 36 | // along with the exports of each module as its value. 37 | 38 | return app.toTree(); 39 | }; 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glimmer-hn", 3 | "version": "0.0.1", 4 | "description": "A brand new Glimmer app.", 5 | "directories": { 6 | "doc": "doc", 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "build": "ember build --environment=production", 11 | "start": "ember server --proxy=https://node-hnapi.herokuapp.com", 12 | "start:prod": "ember serve --environment=production --proxy=https://node-hnapi.herokuapp.com --live-reload=false", 13 | "test": "ember test", 14 | "bench": "node ./benching/index.js", 15 | "serve": "node --expose-gc ./benching/server.js" 16 | }, 17 | "devDependencies": { 18 | "@glimmer/application": "^0.5.1", 19 | "@glimmer/application-pipeline": "^0.7.0", 20 | "@glimmer/component": "^0.4.0", 21 | "@glimmer/resolver": "^0.3.0", 22 | "broccoli-asset-rev": "^2.5.0", 23 | "broccoli-serviceworker": "^0.1.6", 24 | "chrome-tracing": "^0.9.0", 25 | "ember-cli": "github:ember-cli/ember-cli", 26 | "ember-cli-inject-live-reload": "^1.6.1", 27 | "ember-cli-uglify": "^1.2.0", 28 | "express": "^4.8.5", 29 | "glob": "^7.1.1", 30 | "har-remix": "^1.2.0", 31 | "mkdirp": "^0.5.1", 32 | "morgan": "^1.3.2", 33 | "typescript": "^2.2.2" 34 | }, 35 | "engines": { 36 | "node": ">= 4.0" 37 | }, 38 | "private": true, 39 | "dependencies": { 40 | "route-recognizer": "^0.3.3" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/ui/components/glimmer-hn/component.ts: -------------------------------------------------------------------------------- 1 | import Component, { tracked } from "@glimmer/component"; 2 | import { onChange } from '../../../utils/router'; 3 | 4 | let links = document.getElementById('links'); 5 | 6 | let listViews = ['top', 'jobs', 'newest', 'show', 'ask']; 7 | 8 | Array.from(links.querySelectorAll('.item')).forEach((item) => { 9 | links.removeChild(item); 10 | }); 11 | 12 | export default class GlimmerHn extends Component { 13 | nav = document.getElementById('links') 14 | item = 0 15 | cache = {}; 16 | articleCache = {}; 17 | commentsCache = {}; 18 | 19 | @tracked results = []; 20 | @tracked isListView = true; 21 | @tracked isComments = false; 22 | @tracked comments = {}; 23 | @tracked currentArticle = {}; 24 | 25 | constructor(injections) { 26 | super(injections); 27 | 28 | onChange((slug: string, params: any) => { 29 | if (listViews.indexOf(slug) > -1) { 30 | this.isListView = true; 31 | this.isComments = false; 32 | this.fetchListView(slug, params); 33 | return; 34 | } 35 | 36 | if (slug === 'comments') { 37 | this.isComments = true; 38 | if (this.articleCache[params.articleId] !== undefined) { 39 | this.currentArticle = this.articleCache[params.articleId]; 40 | } 41 | 42 | this.fetchComments(params.articleId); 43 | } else { 44 | this.isComments = false; 45 | } 46 | 47 | 48 | this.isListView = false; 49 | }); 50 | } 51 | 52 | fetchComments(id) { 53 | if (this.articleCache[id] !== undefined) { 54 | this.currentArticle = this.articleCache[id]; 55 | } 56 | 57 | if (this.commentsCache[id] !== undefined) { 58 | this.comments = this.commentsCache[id]; 59 | } 60 | 61 | if (this.commentsCache[id] === undefined) { 62 | window['fetch'](`/item/${id}`).then(result => result.json()).then(data => { 63 | this.comments = this.commentsCache[id] = data.comments; 64 | delete data.comments; 65 | this.currentArticle = this.articleCache[id] = data; 66 | }); 67 | } 68 | } 69 | 70 | fetchListView(slug, params) { 71 | let api = slug; 72 | if (slug === 'top') { 73 | api = 'news' 74 | } 75 | 76 | let endpoint = `/${api}`; 77 | 78 | if (this.cache[endpoint] !== undefined) { 79 | this.results = this.cache[endpoint]; 80 | } else { 81 | window['fetch'](endpoint).then((result) => result.json()).then((data) => { 82 | this.results = this.cache[endpoint] = data; 83 | 84 | data.forEach((article) => { 85 | this.articleCache[article.id] = article; 86 | }); 87 | }); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Hello Glimmer 7 | 8 | 9 | 10 | 11 | 12 | {{content-for "head"}} 13 | 14 | {{content-for "head-footer"}} 15 | 339 | 340 | 341 | 342 | {{content-for "body"}} 343 | 344 |
    345 | 346 | 347 | 348 | 349 | {{content-for "body-footer"}} 350 | 351 | 352 | -------------------------------------------------------------------------------- /server/mocks/top.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 'use strict'; 3 | 4 | module.exports = function(app) { 5 | const express = require('express'); 6 | let topRouter = express.Router(); 7 | 8 | topRouter.get('/', function(req, res) { 9 | res.send({ 10 | data: [ 11 | { 12 | "id": 14068253, 13 | "by": "benbreen", 14 | "descendants": 18, 15 | "kids": [ 16 | 14073095, 17 | 14075318, 18 | 14075158, 19 | 14073133 20 | ], 21 | "score": 123, 22 | "time": 1491676205, 23 | "title": "On the Women\u2019s Petition Against Coffee of 1674", 24 | "type": "story", 25 | "url": "https:\/\/resobscura.blogspot.com\/2017\/04\/that-newfangled-abominable-heathenish.html" 26 | }, 27 | { 28 | "by": "wiradikusuma", 29 | "descendants": 6, 30 | "id": 14071939, 31 | "kids": [ 32 | 14072799, 33 | 14075091, 34 | 14075058, 35 | 14074326, 36 | 14074291 37 | ], 38 | "score": 73, 39 | "time": 1491740887, 40 | "title": "PlatformIO, open source ecosystem for IoT", 41 | "type": "story", 42 | "url": "http:\/\/platformio.org\/" 43 | }, 44 | { 45 | "by": "rbanffy", 46 | "descendants": 69, 47 | "id": 14072166, 48 | "kids": [ 49 | 14072474, 50 | 14074419, 51 | 14072718, 52 | 14072254, 53 | 14074690, 54 | 14073116, 55 | 14073262, 56 | 14072714 57 | ], 58 | "score": 166, 59 | "time": 1491744417, 60 | "title": "GCC 7 Release Series \u2013 Changes, New Features, and Fixes", 61 | "type": "story", 62 | "url": "https:\/\/gcc.gnu.org\/gcc-7\/changes.html" 63 | }, 64 | { 65 | "by": "donnemartin", 66 | "descendants": 75, 67 | "id": 14072291, 68 | "kids": [ 69 | 14072605, 70 | 14073437, 71 | 14072636, 72 | 14074531, 73 | 14074132, 74 | 14073261, 75 | 14072685, 76 | 14072567, 77 | 14072665, 78 | 14074260, 79 | 14073518, 80 | 14072901, 81 | 14073034, 82 | 14073414 83 | ], 84 | "score": 132, 85 | "time": 1491746287, 86 | "title": "Unreasonable Ineffectiveness of Machine Learning in Computer Systems Research", 87 | "type": "story", 88 | "url": "https:\/\/www.sigarch.org\/the-unreasonable-ineffectiveness-of-machine-learning-in-computer-systems-research\/" 89 | }, 90 | { 91 | "by": "Tomte", 92 | "descendants": 110, 93 | "id": 14072626, 94 | "kids": [ 95 | 14073126, 96 | 14074883, 97 | 14072832, 98 | 14072969, 99 | 14075569, 100 | 14073600, 101 | 14072874, 102 | 14074931, 103 | 14074896, 104 | 14075084, 105 | 14073317, 106 | 14074392, 107 | 14072859, 108 | 14072930, 109 | 14073324, 110 | 14072890, 111 | 14074537, 112 | 14073401, 113 | 14073444, 114 | 14073256, 115 | 14074051, 116 | 14073012, 117 | 14073498, 118 | 14072827, 119 | 14072971, 120 | 14073097, 121 | 14073004, 122 | 14073086, 123 | 14072922, 124 | 14073403, 125 | 14072947, 126 | 14073303, 127 | 14073304, 128 | 14073149, 129 | 14072967 130 | ], 131 | "score": 263, 132 | "time": 1491750501, 133 | "title": "Textbook manifesto (2016)", 134 | "type": "story", 135 | "url": "http:\/\/greenteapress.com\/wp\/textbook-manifesto\/" 136 | }, 137 | { 138 | "id": 14068253, 139 | "by": "benbreen", 140 | "descendants": 18, 141 | "kids": [ 142 | 14073095, 143 | 14075318, 144 | 14075158, 145 | 14073133 146 | ], 147 | "score": 123, 148 | "time": 1491676205, 149 | "title": "On the Women\u2019s Petition Against Coffee of 1674", 150 | "type": "story", 151 | "url": "https:\/\/resobscura.blogspot.com\/2017\/04\/that-newfangled-abominable-heathenish.html" 152 | }, 153 | { 154 | "id": 14068253, 155 | "by": "benbreen", 156 | "descendants": 18, 157 | "kids": [ 158 | 14073095, 159 | 14075318, 160 | 14075158, 161 | 14073133 162 | ], 163 | "score": 123, 164 | "time": 1491676205, 165 | "title": "On the Women\u2019s Petition Against Coffee of 1674", 166 | "type": "story", 167 | "url": "https:\/\/resobscura.blogspot.com\/2017\/04\/that-newfangled-abominable-heathenish.html" 168 | }, 169 | { 170 | "id": 14068253, 171 | "by": "benbreen", 172 | "descendants": 18, 173 | "kids": [ 174 | 14073095, 175 | 14075318, 176 | 14075158, 177 | 14073133 178 | ], 179 | "score": 123, 180 | "time": 1491676205, 181 | "title": "On the Women\u2019s Petition Against Coffee of 1674", 182 | "type": "story", 183 | "url": "https:\/\/resobscura.blogspot.com\/2017\/04\/that-newfangled-abominable-heathenish.html" 184 | }, 185 | { 186 | "id": 14068253, 187 | "by": "benbreen", 188 | "descendants": 18, 189 | "kids": [ 190 | 14073095, 191 | 14075318, 192 | 14075158, 193 | 14073133 194 | ], 195 | "score": 123, 196 | "time": 1491676205, 197 | "title": "On the Women\u2019s Petition Against Coffee of 1674", 198 | "type": "story", 199 | "url": "https:\/\/resobscura.blogspot.com\/2017\/04\/that-newfangled-abominable-heathenish.html" 200 | }, 201 | { 202 | "id": 14068253, 203 | "by": "benbreen", 204 | "descendants": 18, 205 | "kids": [ 206 | 14073095, 207 | 14075318, 208 | 14075158, 209 | 14073133 210 | ], 211 | "score": 123, 212 | "time": 1491676205, 213 | "title": "On the Women\u2019s Petition Against Coffee of 1674", 214 | "type": "story", 215 | "url": "https:\/\/resobscura.blogspot.com\/2017\/04\/that-newfangled-abominable-heathenish.html" 216 | }, 217 | { 218 | "id": 14068253, 219 | "by": "benbreen", 220 | "descendants": 18, 221 | "kids": [ 222 | 14073095, 223 | 14075318, 224 | 14075158, 225 | 14073133 226 | ], 227 | "score": 123, 228 | "time": 1491676205, 229 | "title": "On the Women\u2019s Petition Against Coffee of 1674", 230 | "type": "story", 231 | "url": "https:\/\/resobscura.blogspot.com\/2017\/04\/that-newfangled-abominable-heathenish.html" 232 | }, { 233 | "id": 14068253, 234 | "by": "benbreen", 235 | "descendants": 18, 236 | "kids": [ 237 | 14073095, 238 | 14075318, 239 | 14075158, 240 | 14073133 241 | ], 242 | "score": 123, 243 | "time": 1491676205, 244 | "title": "On the Women\u2019s Petition Against Coffee of 1674", 245 | "type": "story", 246 | "url": "https:\/\/resobscura.blogspot.com\/2017\/04\/that-newfangled-abominable-heathenish.html" 247 | },{ 248 | "id": 14068253, 249 | "by": "benbreen", 250 | "descendants": 18, 251 | "kids": [ 252 | 14073095, 253 | 14075318, 254 | 14075158, 255 | 14073133 256 | ], 257 | "score": 123, 258 | "time": 1491676205, 259 | "title": "On the Women\u2019s Petition Against Coffee of 1674", 260 | "type": "story", 261 | "url": "https:\/\/resobscura.blogspot.com\/2017\/04\/that-newfangled-abominable-heathenish.html" 262 | },{ 263 | "id": 14068253, 264 | "by": "benbreen", 265 | "descendants": 18, 266 | "kids": [ 267 | 14073095, 268 | 14075318, 269 | 14075158, 270 | 14073133 271 | ], 272 | "score": 123, 273 | "time": 1491676205, 274 | "title": "On the Women\u2019s Petition Against Coffee of 1674", 275 | "type": "story", 276 | "url": "https:\/\/resobscura.blogspot.com\/2017\/04\/that-newfangled-abominable-heathenish.html" 277 | }, 278 | { 279 | "id": 14068253, 280 | "by": "benbreen", 281 | "descendants": 18, 282 | "kids": [ 283 | 14073095, 284 | 14075318, 285 | 14075158, 286 | 14073133 287 | ], 288 | "score": 123, 289 | "time": 1491676205, 290 | "title": "On the Women\u2019s Petition Against Coffee of 1674", 291 | "type": "story", 292 | "url": "https:\/\/resobscura.blogspot.com\/2017\/04\/that-newfangled-abominable-heathenish.html" 293 | },{ 294 | "id": 14068253, 295 | "by": "benbreen", 296 | "descendants": 18, 297 | "kids": [ 298 | 14073095, 299 | 14075318, 300 | 14075158, 301 | 14073133 302 | ], 303 | "score": 123, 304 | "time": 1491676205, 305 | "title": "On the Women\u2019s Petition Against Coffee of 1674", 306 | "type": "story", 307 | "url": "https:\/\/resobscura.blogspot.com\/2017\/04\/that-newfangled-abominable-heathenish.html" 308 | }, 309 | { 310 | "id": 14068253, 311 | "by": "benbreen", 312 | "descendants": 18, 313 | "kids": [ 314 | 14073095, 315 | 14075318, 316 | 14075158, 317 | 14073133 318 | ], 319 | "score": 123, 320 | "time": 1491676205, 321 | "title": "On the Women\u2019s Petition Against Coffee of 1674", 322 | "type": "story", 323 | "url": "https:\/\/resobscura.blogspot.com\/2017\/04\/that-newfangled-abominable-heathenish.html" 324 | }, 325 | { 326 | "id": 14068253, 327 | "by": "benbreen", 328 | "descendants": 18, 329 | "kids": [ 330 | 14073095, 331 | 14075318, 332 | 14075158, 333 | 14073133 334 | ], 335 | "score": 123, 336 | "time": 1491676205, 337 | "title": "On the Women\u2019s Petition Against Coffee of 1674", 338 | "type": "story", 339 | "url": "https:\/\/resobscura.blogspot.com\/2017\/04\/that-newfangled-abominable-heathenish.html" 340 | }, 341 | { 342 | "id": 14068253, 343 | "by": "benbreen", 344 | "descendants": 18, 345 | "kids": [ 346 | 14073095, 347 | 14075318, 348 | 14075158, 349 | 14073133 350 | ], 351 | "score": 123, 352 | "time": 1491676205, 353 | "title": "On the Women\u2019s Petition Against Coffee of 1674", 354 | "type": "story", 355 | "url": "https:\/\/resobscura.blogspot.com\/2017\/04\/that-newfangled-abominable-heathenish.html" 356 | }, 357 | { 358 | "id": 14068253, 359 | "by": "benbreen", 360 | "descendants": 18, 361 | "kids": [ 362 | 14073095, 363 | 14075318, 364 | 14075158, 365 | 14073133 366 | ], 367 | "score": 123, 368 | "time": 1491676205, 369 | "title": "On the Women\u2019s Petition Against Coffee of 1674", 370 | "type": "story", 371 | "url": "https:\/\/resobscura.blogspot.com\/2017\/04\/that-newfangled-abominable-heathenish.html" 372 | } 373 | ], 374 | meta: { 375 | type: 'top', 376 | from: 0, 377 | to: 20, 378 | max: 359, 379 | pages: 18 380 | } 381 | }); 382 | }); 383 | 384 | topRouter.post('/', function(req, res) { 385 | res.status(201).end(); 386 | }); 387 | 388 | topRouter.get('/:id', function(req, res) { 389 | res.send({ 390 | 'top': { 391 | id: req.params.id 392 | } 393 | }); 394 | }); 395 | 396 | topRouter.put('/:id', function(req, res) { 397 | res.send({ 398 | 'top': { 399 | id: req.params.id 400 | } 401 | }); 402 | }); 403 | 404 | topRouter.delete('/:id', function(req, res) { 405 | res.status(204).end(); 406 | }); 407 | 408 | // The POST and PUT call will not contain a request body 409 | // because the body-parser is not included by default. 410 | // To use req.body, run: 411 | 412 | // npm install --save-dev body-parser 413 | 414 | // After installing, you need to `use` the body-parser for 415 | // this mock uncommenting the following line: 416 | // 417 | //app.use('/api/top', require('body-parser').json()); 418 | app.use('/api/top', topRouter); 419 | }; 420 | --------------------------------------------------------------------------------