├── vendor └── .gitkeep ├── app ├── helpers │ └── .gitkeep ├── templates │ ├── user.hbs │ ├── application.hbs │ ├── ask.hbs │ ├── new.hbs │ ├── top.hbs │ ├── jobs.hbs │ ├── show.hbs │ ├── components │ │ ├── card-list.hbs │ │ ├── pagination-links.hbs │ │ ├── nav-bar.hbs │ │ ├── comment-list.hbs │ │ ├── item-details.hbs │ │ └── item-card.hbs │ └── item.hbs ├── resolver.js ├── styles │ ├── variables.scss │ ├── phone.scss │ ├── components │ │ ├── item-details.scss │ │ ├── nav-bar.scss │ │ ├── comment-list.scss │ │ ├── item-card.scss │ │ └── card-list.scss │ └── app.scss ├── components │ ├── card-list.js │ ├── item-card.js │ ├── comment-list.js │ ├── item-details.js │ ├── nav-bar.js │ └── pagination-links.js ├── controllers │ ├── ask.js │ ├── jobs.js │ ├── new.js │ ├── show.js │ └── top.js ├── models │ ├── ask-story.js │ ├── jobs-story.js │ ├── new-story.js │ ├── show-story.js │ ├── top-story.js │ ├── comment.js │ ├── user.js │ └── item.js ├── routes │ ├── index.js │ ├── item.js │ ├── ask.js │ ├── new.js │ ├── top.js │ ├── jobs.js │ └── show.js ├── serializers │ ├── comment.js │ └── item.js ├── transforms │ └── unix-date.js ├── app.js ├── adapters │ ├── ask-story.js │ ├── jobs-story.js │ ├── show-story.js │ ├── top-story.js │ ├── new-story.js │ └── application.js ├── router.js └── index.html ├── .watchmanconfig ├── public ├── robots.txt ├── assets │ └── images │ │ ├── icon-192x192.png │ │ └── icon-512x512.png └── crossdomain.xml ├── .bowerrc ├── .firebaserc ├── tests ├── helpers │ ├── destroy-app.js │ ├── resolver.js │ ├── start-app.js │ └── module-for-acceptance.js ├── test-helper.js ├── unit │ ├── helpers │ │ └── add-test.js │ ├── routes │ │ ├── ask-test.js │ │ ├── jobs-test.js │ │ ├── new-test.js │ │ ├── show-test.js │ │ └── top-test.js │ ├── models │ │ ├── item-test.js │ │ ├── user-test.js │ │ ├── ask-story-test.js │ │ ├── new-story-test.js │ │ ├── top-story-test.js │ │ ├── jobs-story-test.js │ │ └── show-story-test.js │ ├── adapters │ │ ├── item-test.js │ │ ├── ask-story-test.js │ │ ├── jobs-story-test.js │ │ ├── new-story-test.js │ │ ├── show-story-test.js │ │ └── top-story-test.js │ ├── controllers │ │ └── top-test.js │ ├── transforms │ │ └── unix-date-test.js │ └── serializers │ │ └── item-test.js ├── integration │ └── components │ │ ├── nav-bar-test.js │ │ ├── card-list-test.js │ │ ├── item-card-test.js │ │ ├── comment-list-test.js │ │ ├── item-details-test.js │ │ └── list-pagination-test.js └── index.html ├── bower.json ├── firebase.json ├── testem.js ├── .ember-cli ├── .gitignore ├── .travis.yml ├── .editorconfig ├── config ├── manifest.js ├── deploy.js └── environment.js ├── .jshintrc ├── LICENSE ├── ember-cli-build.js ├── README.md ├── package.json └── .eslintrc.js /vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/templates/user.hbs: -------------------------------------------------------------------------------- 1 | Coming soon. 2 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | {{nav-bar}} 2 | 3 | {{outlet}} 4 | -------------------------------------------------------------------------------- /app/templates/ask.hbs: -------------------------------------------------------------------------------- 1 | {{card-list items=model.items modelName='ask' page=page}} 2 | -------------------------------------------------------------------------------- /app/templates/new.hbs: -------------------------------------------------------------------------------- 1 | {{card-list items=model.items modelName='new' page=page}} 2 | -------------------------------------------------------------------------------- /app/templates/top.hbs: -------------------------------------------------------------------------------- 1 | {{card-list items=model.items modelName='top' page=page}} 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "analytics": false 4 | } 5 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "hnpwa-ember" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /app/templates/jobs.hbs: -------------------------------------------------------------------------------- 1 | {{card-list items=model.items modelName='jobs' page=page}} 2 | -------------------------------------------------------------------------------- /app/templates/show.hbs: -------------------------------------------------------------------------------- 1 | {{card-list items=model.items modelName='show' page=page}} 2 | -------------------------------------------------------------------------------- /app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /app/styles/variables.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | $primary: #ff6600; 3 | $secondary: #ffffff; 4 | 5 | // Breakpoints 6 | $phone: 768px; 7 | -------------------------------------------------------------------------------- /public/assets/images/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stereobooster/hnpwa-ember/master/public/assets/images/icon-192x192.png -------------------------------------------------------------------------------- /public/assets/images/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stereobooster/hnpwa-ember/master/public/assets/images/icon-512x512.png -------------------------------------------------------------------------------- /app/components/card-list.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Component.extend({ 4 | classNames: ['card-list'] 5 | }); 6 | -------------------------------------------------------------------------------- /app/components/item-card.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Component.extend({ 4 | classNames: ['item-card'] 5 | }); 6 | -------------------------------------------------------------------------------- /app/components/comment-list.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Component.extend({ 4 | classNames: ['comment-list'] 5 | }); 6 | -------------------------------------------------------------------------------- /app/components/item-details.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Component.extend({ 4 | classNames: ['item-details'] 5 | }); 6 | -------------------------------------------------------------------------------- /app/controllers/ask.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Controller.extend({ 4 | queryParams: ['page'], 5 | 6 | page: 1 7 | }); 8 | -------------------------------------------------------------------------------- /app/controllers/jobs.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Controller.extend({ 4 | queryParams: ['page'], 5 | 6 | page: 1 7 | }); 8 | -------------------------------------------------------------------------------- /app/controllers/new.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Controller.extend({ 4 | queryParams: ['page'], 5 | 6 | page: 1 7 | }); 8 | -------------------------------------------------------------------------------- /app/controllers/show.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Controller.extend({ 4 | queryParams: ['page'], 5 | 6 | page: 1 7 | }); 8 | -------------------------------------------------------------------------------- /app/controllers/top.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Controller.extend({ 4 | queryParams: ['page'], 5 | 6 | page: 1 7 | }); 8 | -------------------------------------------------------------------------------- /app/models/ask-story.js: -------------------------------------------------------------------------------- 1 | import Item from './item'; 2 | import attr from 'ember-data/attr'; 3 | 4 | export default Item.extend({ 5 | user: attr('string') 6 | }); 7 | -------------------------------------------------------------------------------- /app/models/jobs-story.js: -------------------------------------------------------------------------------- 1 | import Item from './item'; 2 | import attr from 'ember-data/attr'; 3 | 4 | export default Item.extend({ 5 | user: attr('string') 6 | }); 7 | -------------------------------------------------------------------------------- /app/models/new-story.js: -------------------------------------------------------------------------------- 1 | import Item from './item'; 2 | import attr from 'ember-data/attr'; 3 | 4 | export default Item.extend({ 5 | user: attr('string') 6 | }); 7 | -------------------------------------------------------------------------------- /app/models/show-story.js: -------------------------------------------------------------------------------- 1 | import Item from './item'; 2 | import attr from 'ember-data/attr'; 3 | 4 | export default Item.extend({ 5 | user: attr('string') 6 | }); 7 | -------------------------------------------------------------------------------- /app/models/top-story.js: -------------------------------------------------------------------------------- 1 | import Item from './item'; 2 | import attr from 'ember-data/attr'; 3 | 4 | export default Item.extend({ 5 | user: attr('string') 6 | }); 7 | -------------------------------------------------------------------------------- /app/routes/index.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | beforeModel() { 5 | this.transitionTo('top'); 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /tests/helpers/destroy-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default function destroyApp(application) { 4 | Ember.run(application, 'destroy'); 5 | } 6 | -------------------------------------------------------------------------------- /app/templates/components/card-list.hbs: -------------------------------------------------------------------------------- 1 | {{#each items as |story|}} 2 | {{item-card item=story}} 3 | {{/each}} 4 | 5 | {{pagination-links modelName=modelName page=page count=items.length}} 6 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hnpwa-ember", 3 | "dependencies": { 4 | "ember": "2.12.2", 5 | "ember-cli-shims": "0.1.1", 6 | "ember-qunit-notifications": "0.1.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /app/serializers/comment.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | export default DS.JSONSerializer.extend(DS.EmbeddedRecordsMixin, { 4 | attrs: { 5 | comments: { embedded: 'always' } 6 | } 7 | }); 8 | -------------------------------------------------------------------------------- /app/models/comment.js: -------------------------------------------------------------------------------- 1 | import Item from './item'; 2 | import attr from 'ember-data/attr'; 3 | 4 | export default Item.extend({ 5 | type: 'comment', 6 | user: attr('string'), 7 | level: attr('number') 8 | }); 9 | -------------------------------------------------------------------------------- /tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import resolver from './helpers/resolver'; 2 | import { 3 | setResolver 4 | } from 'ember-qunit'; 5 | import { start } from 'ember-cli-qunit'; 6 | 7 | setResolver(resolver); 8 | start(); 9 | -------------------------------------------------------------------------------- /app/templates/item.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{item-details item=model.item}} 3 | 4 | {{#if model.item.hasChildren}} 5 | {{comment-list item=model.item comments=model.item.comments}} 6 | {{/if}} 7 |
8 | -------------------------------------------------------------------------------- /app/serializers/item.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | export default DS.JSONSerializer.extend(DS.EmbeddedRecordsMixin, { 4 | attrs: { 5 | user: { embedded: 'always' }, 6 | comments: { embedded: 'always' } 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /app/routes/item.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model(params) { 5 | return Ember.RSVP.hash({ 6 | item: this.store.findRecord('item', params.item_id) 7 | }); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "dist", 4 | "ignore": [ 5 | "**/vendor*", 6 | "**/hnpwa*" 7 | ], 8 | "rewrites": [ 9 | { 10 | "source": "**", 11 | "destination": "/index.html" 12 | } 13 | ] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/transforms/unix-date.js: -------------------------------------------------------------------------------- 1 | import DS from 'ember-data'; 2 | 3 | export default DS.Transform.extend({ 4 | deserialize(serialized) { 5 | return new Date(serialized * 1000); 6 | }, 7 | 8 | serialize(deserialized) { 9 | return deserialized.toISOString(); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /testem.js: -------------------------------------------------------------------------------- 1 | /*jshint node:true*/ 2 | module.exports = { 3 | "framework": "qunit", 4 | "test_page": "tests/index.html?hidepassed", 5 | "disable_watching": true, 6 | "launch_in_ci": [ 7 | "PhantomJS" 8 | ], 9 | "launch_in_dev": [ 10 | "PhantomJS", 11 | "Chrome" 12 | ] 13 | }; 14 | -------------------------------------------------------------------------------- /.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /tests/helpers/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from '../../resolver'; 2 | import config from '../../config/environment'; 3 | 4 | const resolver = Resolver.create(); 5 | 6 | resolver.namespace = { 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix 9 | }; 10 | 11 | export default resolver; 12 | -------------------------------------------------------------------------------- /tests/unit/helpers/add-test.js: -------------------------------------------------------------------------------- 1 | 2 | import { add } from 'hnpwa-ember/helpers/add'; 3 | import { module, test } from 'qunit'; 4 | 5 | module('Unit | Helper | add'); 6 | 7 | // Replace this with your real tests. 8 | test('it works', function(assert) { 9 | let result = add([42]); 10 | assert.ok(result); 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /app/routes/ask.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model(params) { 5 | return Ember.RSVP.hash({ 6 | items: this.store.query('ask-story', { page: params.page || 1 }) 7 | }); 8 | }, 9 | 10 | queryParams: { 11 | page: { 12 | refreshModel: true 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /app/routes/new.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model(params) { 5 | return Ember.RSVP.hash({ 6 | items: this.store.query('new-story', { page: params.page || 1 }) 7 | }); 8 | }, 9 | 10 | queryParams: { 11 | page: { 12 | refreshModel: true 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /app/routes/top.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model(params) { 5 | return Ember.RSVP.hash({ 6 | items: this.store.query('top-story', { page: params.page || 1 }) 7 | }); 8 | }, 9 | 10 | queryParams: { 11 | page: { 12 | refreshModel: true 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/* 15 | /libpeerconnection.log 16 | npm-debug.log 17 | testem.log 18 | 19 | .env 20 | -------------------------------------------------------------------------------- /app/routes/jobs.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model(params) { 5 | return Ember.RSVP.hash({ 6 | items: this.store.query('jobs-story', { page: params.page || 1 }) 7 | }); 8 | }, 9 | 10 | queryParams: { 11 | page: { 12 | refreshModel: true 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /app/routes/show.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Route.extend({ 4 | model(params) { 5 | return Ember.RSVP.hash({ 6 | items: this.store.query('show-story', { page: params.page || 1 }) 7 | }); 8 | }, 9 | 10 | queryParams: { 11 | page: { 12 | refreshModel: true 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /app/templates/components/pagination-links.hbs: -------------------------------------------------------------------------------- 1 | {{#if showPrevious}} 2 | {{#link-to modelName (query-params page=previousPage)}}<< Prev{{/link-to}} 3 | {{else}} 4 |
5 | {{/if}} 6 | 7 | {{#if showNext}} 8 | {{#link-to modelName (query-params page=nextPage)}}Next >>{{/link-to}} 9 | {{else}} 10 |
11 | {{/if}} 12 | -------------------------------------------------------------------------------- /tests/unit/routes/ask-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('route:ask', 'Unit | Route | ask', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | let route = this.subject(); 10 | assert.ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/routes/jobs-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('route:jobs', 'Unit | Route | jobs', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | let route = this.subject(); 10 | assert.ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/routes/new-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('route:new', 'Unit | Route | new', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | let route = this.subject(); 10 | assert.ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/routes/show-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('route:show', 'Unit | Route | show', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | let route = this.subject(); 10 | assert.ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/routes/top-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('route:top', 'Unit | Route | top', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | let route = this.subject(); 10 | assert.ok(route); 11 | }); 12 | -------------------------------------------------------------------------------- /tests/unit/models/item-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForModel, test } from 'ember-qunit'; 2 | 3 | moduleForModel('item', 'Unit | Model | item', { 4 | // Specify the other units that are required for this test. 5 | needs: [] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | let model = this.subject(); 10 | // let store = this.store(); 11 | assert.ok(Boolean(model)); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/models/user-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForModel, test } from 'ember-qunit'; 2 | 3 | moduleForModel('user', 'Unit | Model | user', { 4 | // Specify the other units that are required for this test. 5 | needs: [] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | let model = this.subject(); 10 | // let store = this.store(); 11 | assert.ok(Boolean(model)); 12 | }); 13 | -------------------------------------------------------------------------------- /app/models/user.js: -------------------------------------------------------------------------------- 1 | import Model from 'ember-data/model'; 2 | import attr from 'ember-data/attr'; 3 | 4 | export default Model.extend({ 5 | created_time: attr('unix-date', { 6 | defaultValue: function() { 7 | return new Date(); 8 | } 9 | }), 10 | karma: attr('number', { 11 | defaultValue: 0 12 | }), 13 | avg: attr('string'), 14 | about: attr('string') 15 | }); 16 | -------------------------------------------------------------------------------- /tests/unit/models/ask-story-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForModel, test } from 'ember-qunit'; 2 | 3 | moduleForModel('ask-story', 'Unit | Model | ask story', { 4 | // Specify the other units that are required for this test. 5 | needs: [] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | let model = this.subject(); 10 | // let store = this.store(); 11 | assert.ok(Boolean(model)); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/models/new-story-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForModel, test } from 'ember-qunit'; 2 | 3 | moduleForModel('new-story', 'Unit | Model | new story', { 4 | // Specify the other units that are required for this test. 5 | needs: [] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | let model = this.subject(); 10 | // let store = this.store(); 11 | assert.ok(Boolean(model)); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/models/top-story-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForModel, test } from 'ember-qunit'; 2 | 3 | moduleForModel('top-story', 'Unit | Model | top story', { 4 | // Specify the other units that are required for this test. 5 | needs: [] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | let model = this.subject(); 10 | // let store = this.store(); 11 | assert.ok(Boolean(model)); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/models/jobs-story-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForModel, test } from 'ember-qunit'; 2 | 3 | moduleForModel('jobs-story', 'Unit | Model | jobs story', { 4 | // Specify the other units that are required for this test. 5 | needs: [] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | let model = this.subject(); 10 | // let store = this.store(); 11 | assert.ok(Boolean(model)); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/models/show-story-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForModel, test } from 'ember-qunit'; 2 | 3 | moduleForModel('show-story', 'Unit | Model | show story', { 4 | // Specify the other units that are required for this test. 5 | needs: [] 6 | }); 7 | 8 | test('it exists', function(assert) { 9 | let model = this.subject(); 10 | // let store = this.store(); 11 | assert.ok(Boolean(model)); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/adapters/item-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('adapter:item', 'Unit | Adapter | item', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['serializer:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function(assert) { 10 | let adapter = this.subject(); 11 | assert.ok(adapter); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/controllers/top-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('controller:top', 'Unit | Controller | top', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['controller:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function(assert) { 10 | let controller = this.subject(); 11 | assert.ok(controller); 12 | }); 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "4" 5 | 6 | sudo: false 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | 12 | before_install: 13 | - npm config set spin false 14 | - npm install -g bower 15 | - bower --version 16 | - npm install phantomjs-prebuilt 17 | - phantomjs --version 18 | 19 | install: 20 | - npm install 21 | - bower install 22 | 23 | script: 24 | - npm test 25 | -------------------------------------------------------------------------------- /app/styles/phone.scss: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | @media only screen and (max-width: $phone) { 4 | .details, .card-list { 5 | width: 100%; 6 | overflow: hidden; 7 | } 8 | 9 | .comment-list { 10 | width: calc(100% - 10px); 11 | 12 | .comment { 13 | margin: 5px; 14 | 15 | &-children { 16 | margin-left: 5px; 17 | border-width: 2px; 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/unit/adapters/ask-story-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('adapter:ask-story', 'Unit | Adapter | ask story', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['serializer:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function(assert) { 10 | let adapter = this.subject(); 11 | assert.ok(adapter); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/adapters/jobs-story-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('adapter:jobs-story', 'Unit | Adapter | jobs story', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['serializer:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function(assert) { 10 | let adapter = this.subject(); 11 | assert.ok(adapter); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/adapters/new-story-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('adapter:new-story', 'Unit | Adapter | new story', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['serializer:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function(assert) { 10 | let adapter = this.subject(); 11 | assert.ok(adapter); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/adapters/show-story-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('adapter:show-story', 'Unit | Adapter | show story', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['serializer:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function(assert) { 10 | let adapter = this.subject(); 11 | assert.ok(adapter); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/adapters/top-story-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('adapter:top-story', 'Unit | Adapter | top story', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['serializer:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function(assert) { 10 | let adapter = this.subject(); 11 | assert.ok(adapter); 12 | }); 13 | -------------------------------------------------------------------------------- /tests/unit/transforms/unix-date-test.js: -------------------------------------------------------------------------------- 1 | import { moduleFor, test } from 'ember-qunit'; 2 | 3 | moduleFor('transform:unix-date', 'Unit | Transform | unix date', { 4 | // Specify the other units that are required for this test. 5 | // needs: ['serializer:foo'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it exists', function(assert) { 10 | let transform = this.subject(); 11 | assert.ok(transform); 12 | }); 13 | -------------------------------------------------------------------------------- /app/components/nav-bar.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Component.extend({ 4 | classNames: ['navbar'], 5 | routing: Ember.inject.service('-routing'), 6 | 7 | links: [ 8 | 'top', 9 | 'new', 10 | 'show', 11 | 'ask', 12 | 'jobs' 13 | ], 14 | 15 | activePath: Ember.computed('routing.currentRouteName', function() { 16 | return this.get('routing.currentRouteName'); 17 | }) 18 | }); 19 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Resolver from './resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | 6 | let App; 7 | 8 | Ember.MODEL_FACTORY_INJECTIONS = true; 9 | 10 | App = Ember.Application.extend({ 11 | modulePrefix: config.modulePrefix, 12 | podModulePrefix: config.podModulePrefix, 13 | Resolver 14 | }); 15 | 16 | loadInitializers(App, config.modulePrefix); 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /tests/unit/serializers/item-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForModel, test } from 'ember-qunit'; 2 | 3 | moduleForModel('item', 'Unit | Serializer | item', { 4 | // Specify the other units that are required for this test. 5 | needs: ['serializer:item'] 6 | }); 7 | 8 | // Replace this with your real tests. 9 | test('it serializes records', function(assert) { 10 | let record = this.subject(), 11 | serializedRecord = record.serialize(); 12 | 13 | assert.ok(serializedRecord); 14 | }); 15 | -------------------------------------------------------------------------------- /app/templates/components/nav-bar.hbs: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /app/styles/components/item-details.scss: -------------------------------------------------------------------------------- 1 | .item-details { 2 | width: calc(100% - 40px;); 3 | background-color: $secondary; 4 | margin-bottom: 10px; 5 | padding: 20px; 6 | 7 | &-header { 8 | margin-bottom: 20px; 9 | font-size: 20px; 10 | font-weight: bold; 11 | 12 | &-byline { 13 | font-size: 12px; 14 | font-weight: normal; 15 | 16 | a:hover { 17 | color: $primary; 18 | } 19 | } 20 | } 21 | 22 | &-body { 23 | a { 24 | color: $primary; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/styles/components/nav-bar.scss: -------------------------------------------------------------------------------- 1 | .navbar { 2 | background-color: $primary; 3 | 4 | &-links { 5 | list-style: none; 6 | 7 | display: flex; 8 | align-items: center; 9 | 10 | margin: 0; 11 | padding: 10px; 12 | } 13 | 14 | &-link { 15 | margin: 0px; 16 | padding: 5px; 17 | 18 | text-transform: capitalize; 19 | 20 | color: $secondary; 21 | 22 | .brand { 23 | border: 1px solid $secondary; 24 | } 25 | } 26 | 27 | .active { 28 | color: $secondary; 29 | font-weight: bold; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/helpers/start-app.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import Application from '../../app'; 3 | import config from '../../config/environment'; 4 | 5 | export default function startApp(attrs) { 6 | let application, 7 | attributes = Ember.merge({}, config.APP); 8 | 9 | // use defaults, but you can override; 10 | attributes = Ember.merge(attributes, attrs); 11 | 12 | Ember.run(() => { 13 | application = Application.create(attributes); 14 | application.setupForTesting(); 15 | application.injectTestHelpers(); 16 | }); 17 | 18 | return application; 19 | } 20 | -------------------------------------------------------------------------------- /app/templates/components/comment-list.hbs: -------------------------------------------------------------------------------- 1 | {{#each comments as |comment|}} 2 |
3 |
4 | {{#link-to 'user' comment.user}} 5 | {{comment.user}} 6 | {{/link-to}} 7 | 8 | {{comment.time_ago}} 9 |
10 | 11 |
12 | {{comment.commentText}} 13 |
14 | 15 | {{#if comment.hasChildren}} 16 |
17 | {{comment-list item=comment comments=comment.activeChildren}} 18 |
19 | {{/if}} 20 |
21 | {{/each}} 22 | -------------------------------------------------------------------------------- /config/manifest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function() { 4 | return { 5 | name: 'hnpwa-ember', 6 | short_name: 'hnpwa-ember', 7 | description: '', 8 | start_url: '/top', 9 | display: 'standalone', 10 | background_color: '#fff', 11 | theme_color: '#ff6600', 12 | icons: [ 13 | { 14 | src: '/assets/images/icon-192x192.png', 15 | sizes: '192x192', 16 | type: 'image/png' 17 | }, 18 | { 19 | src: '/assets/images/icon-512x512.png', 20 | sizes: '512x512', 21 | type: 'image/png' 22 | } 23 | ] 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /app/styles/components/comment-list.scss: -------------------------------------------------------------------------------- 1 | .comment-list { 2 | width: calc(100% - 20px); 3 | background-color: $secondary; 4 | padding: 10px 10px 0 10px; 5 | 6 | .comment { 7 | margin: 10px; 8 | 9 | &-header { 10 | margin-bottom: 15px; 11 | font-size: 14px; 12 | 13 | a:hover { 14 | color: $primary; 15 | } 16 | } 17 | 18 | &-body { 19 | font-size: 12px; 20 | margin-bottom: 10px; 21 | 22 | a { 23 | color: $primary; 24 | } 25 | } 26 | 27 | &-children { 28 | margin-left: 15px; 29 | border-left: 5px solid #666666; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/components/pagination-links.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | 3 | export default Ember.Component.extend({ 4 | classNames: ['pagination'], 5 | 6 | previousPage: Ember.computed('page', function() { 7 | return Number.parseInt(this.get('page'), 10) - 1; 8 | }), 9 | 10 | showPrevious: Ember.computed('page', function() { 11 | return Number.parseInt(this.get('page'), 10) > 1; 12 | }), 13 | 14 | nextPage: Ember.computed('page', function() { 15 | return Number.parseInt(this.get('page'), 10) + 1; 16 | }), 17 | 18 | showNext: Ember.computed('count', function() { 19 | return this.get('count') === 30; 20 | }) 21 | }); 22 | -------------------------------------------------------------------------------- /app/adapters/ask-story.js: -------------------------------------------------------------------------------- 1 | import ApplicationAdapter from './application'; 2 | import Ember from 'ember'; 3 | import ENV from 'hnpwa-ember/config/environment'; 4 | import fetch from 'fetch'; 5 | 6 | export default ApplicationAdapter.extend({ 7 | query(store, type, filter, _snapshot) { 8 | let page = filter.page; 9 | 10 | return new Ember.RSVP.Promise(function(resolve, reject) { 11 | fetch(`${ENV.apiUrl}/ask?page=${page}`).then(function(response) { 12 | response.json().then((data) => { 13 | resolve(data); 14 | }); 15 | }, function(jqXHR) { 16 | reject(jqXHR); 17 | }); 18 | }); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /app/adapters/jobs-story.js: -------------------------------------------------------------------------------- 1 | import ApplicationAdapter from './application'; 2 | import Ember from 'ember'; 3 | import ENV from 'hnpwa-ember/config/environment'; 4 | import fetch from 'fetch'; 5 | 6 | export default ApplicationAdapter.extend({ 7 | query(store, type, filter, _snapshot) { 8 | let page = filter.page; 9 | 10 | return new Ember.RSVP.Promise(function(resolve, reject) { 11 | fetch(`${ENV.apiUrl}/jobs?page=${page}`).then(function(response) { 12 | response.json().then((data) => { 13 | resolve(data); 14 | }); 15 | }, function(jqXHR) { 16 | reject(jqXHR); 17 | }); 18 | }); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /app/adapters/show-story.js: -------------------------------------------------------------------------------- 1 | import ApplicationAdapter from './application'; 2 | import Ember from 'ember'; 3 | import ENV from 'hnpwa-ember/config/environment'; 4 | import fetch from 'fetch'; 5 | 6 | export default ApplicationAdapter.extend({ 7 | query(store, type, filter, _snapshot) { 8 | let page = filter.page; 9 | 10 | return new Ember.RSVP.Promise(function(resolve, reject) { 11 | fetch(`${ENV.apiUrl}/show?page=${page}`).then(function(response) { 12 | response.json().then((data) => { 13 | resolve(data); 14 | }); 15 | }, function(jqXHR) { 16 | reject(jqXHR); 17 | }); 18 | }); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /app/adapters/top-story.js: -------------------------------------------------------------------------------- 1 | import ApplicationAdapter from './application'; 2 | import Ember from 'ember'; 3 | import ENV from 'hnpwa-ember/config/environment'; 4 | import fetch from 'fetch'; 5 | 6 | export default ApplicationAdapter.extend({ 7 | query(store, type, filter, _snapshot) { 8 | let page = filter.page; 9 | 10 | return new Ember.RSVP.Promise(function(resolve, reject) { 11 | fetch(`${ENV.apiUrl}/news?page=${page}`).then(function(response) { 12 | response.json().then((data) => { 13 | resolve(data); 14 | }); 15 | }, function(jqXHR) { 16 | reject(jqXHR); 17 | }); 18 | }); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "predef": [ 3 | "document", 4 | "window", 5 | "-Promise" 6 | ], 7 | "browser": true, 8 | "boss": true, 9 | "curly": true, 10 | "debug": false, 11 | "devel": true, 12 | "eqeqeq": true, 13 | "evil": true, 14 | "forin": false, 15 | "immed": false, 16 | "laxbreak": false, 17 | "newcap": true, 18 | "noarg": true, 19 | "noempty": false, 20 | "nonew": false, 21 | "nomen": false, 22 | "onevar": false, 23 | "plusplus": false, 24 | "regexp": false, 25 | "undef": true, 26 | "sub": true, 27 | "strict": false, 28 | "white": false, 29 | "eqnull": true, 30 | "esversion": 6, 31 | "unused": true 32 | } 33 | -------------------------------------------------------------------------------- /app/adapters/new-story.js: -------------------------------------------------------------------------------- 1 | import ApplicationAdapter from './application'; 2 | import Ember from 'ember'; 3 | import ENV from 'hnpwa-ember/config/environment'; 4 | import fetch from 'fetch'; 5 | 6 | export default ApplicationAdapter.extend({ 7 | query(store, type, filter, _snapshot) { 8 | let page = filter.page; 9 | 10 | return new Ember.RSVP.Promise(function(resolve, reject) { 11 | fetch(`${ENV.apiUrl}/newest?page=${page}`).then(function(response) { 12 | response.json().then((data) => { 13 | resolve(data); 14 | }); 15 | }, function(jqXHR) { 16 | reject(jqXHR); 17 | }); 18 | }); 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /app/styles/components/item-card.scss: -------------------------------------------------------------------------------- 1 | .item-card { 2 | display: flex; 3 | align-items: center; 4 | border-bottom: 1px solid #eeeeee; 5 | margin: 5px 10px; 6 | padding: 5px; 7 | color: #333333; 8 | 9 | &-position { 10 | text-align: center; 11 | flex-basis: 50px; 12 | flex-shrink: 0; 13 | } 14 | 15 | &-body { 16 | flex-grow: 1; 17 | padding-left: 10px; 18 | 19 | &-header { 20 | font-size: 18px; 21 | } 22 | 23 | &-footer { 24 | font-size: 14px; 25 | 26 | a:hover { 27 | color: $primary; 28 | } 29 | } 30 | } 31 | 32 | .muted { 33 | color: #999999; 34 | font-size: 12px; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/styles/components/card-list.scss: -------------------------------------------------------------------------------- 1 | .card-list { 2 | width: 75%; 3 | background-color: $secondary; 4 | display: flex; 5 | flex-direction: column; 6 | align-self: center; 7 | overflow-y: scroll; 8 | 9 | & > * { 10 | flex: 1 1 auto; 11 | } 12 | 13 | .pagination { 14 | display: flex; 15 | justify-content: space-between; 16 | align-items: center; 17 | height: 30px; 18 | padding: 10px; 19 | 20 | .pagination-button { 21 | background-color: inherit; 22 | border: none; 23 | text-align: center; 24 | text-decoration: none; 25 | display: inline-block; 26 | font-size: 16px; 27 | cursor: pointer; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/styles/app.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | 3 | @import 'components/nav-bar'; 4 | @import 'components/card-list'; 5 | @import 'components/item-card'; 6 | @import 'components/comment-list'; 7 | @import 'components/item-details'; 8 | 9 | html, body { 10 | margin: 0; 11 | font-family: Arial, sans-serif; 12 | 13 | background-color: #eeeeee; 14 | width: 100%; 15 | height: 100%; 16 | } 17 | 18 | a, a:hover, a:visited { 19 | color: inherit; 20 | text-decoration: none; 21 | } 22 | 23 | .ember-application > .ember-view { 24 | display: flex; 25 | flex-direction: column; 26 | } 27 | 28 | .details { 29 | display: flex; 30 | flex-direction: column; 31 | align-items: center; 32 | align-self: center; 33 | width: 75%; 34 | } 35 | -------------------------------------------------------------------------------- /tests/helpers/module-for-acceptance.js: -------------------------------------------------------------------------------- 1 | import { module } from 'qunit'; 2 | import Ember from 'ember'; 3 | import startApp from '../helpers/start-app'; 4 | import destroyApp from '../helpers/destroy-app'; 5 | 6 | const { RSVP: { Promise } } = Ember; 7 | 8 | export default function(name, options = {}) { 9 | module(name, { 10 | beforeEach() { 11 | this.application = startApp(); 12 | 13 | if(options.beforeEach) { 14 | return options.beforeEach.apply(this, arguments); 15 | } 16 | }, 17 | 18 | afterEach() { 19 | let afterEach = options.afterEach && options.afterEach.apply(this, arguments); 20 | return Promise.resolve(afterEach).then(() => destroyApp(this.application)); 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /app/adapters/application.js: -------------------------------------------------------------------------------- 1 | import Adapter from 'ember-data/adapter'; 2 | import Ember from 'ember'; 3 | import ENV from 'hnpwa-ember/config/environment'; 4 | import fetch from 'fetch'; 5 | 6 | export default Adapter.extend({ 7 | findRecord(store, type, id, _snapshot) { 8 | return new Ember.RSVP.Promise(function(resolve, reject) { 9 | fetch(`${ENV.apiUrl}/${type.modelName}/${id}`).then(function(response) { 10 | response.json().then((data) => { 11 | if(data === null) { 12 | resolve({ 13 | id: id 14 | }); 15 | } else { 16 | resolve(data); 17 | } 18 | }); 19 | }, function(jqXHR) { 20 | reject(jqXHR); 21 | }); 22 | }); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /app/router.js: -------------------------------------------------------------------------------- 1 | import Ember from 'ember'; 2 | import config from './config/environment'; 3 | 4 | const Router = Ember.Router.extend({ 5 | location: config.locationType, 6 | rootURL: config.rootURL 7 | }); 8 | 9 | // eslint-disable-next-line array-callback-return 10 | Router.map(function() { 11 | // Top-level navigation 12 | this.route('index', { path: '/' }); 13 | this.route('top'); 14 | this.route('new'); 15 | this.route('show'); 16 | this.route('ask'); 17 | this.route('jobs'); 18 | 19 | // Article and user pages 20 | this.route('item', { path: '/item/:item_id' }); 21 | this.route('user', { path: '/user/:user_id' }); 22 | }); 23 | 24 | export default Router; 25 | 26 | /* 27 | https://hacker-news.firebaseio.com/v0/beststories 28 | */ 29 | -------------------------------------------------------------------------------- /app/templates/components/item-details.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{#if item.isExternal}} 3 | 4 | {{item.title}} 5 | 6 | 7 | 8 | {{item.domain}} 9 | 10 | {{else}} 11 | {{item.title}} 12 | {{/if}} 13 | 14 |
15 | {{item.score}} points 16 | 17 | 18 | by 19 | 20 | {{#link-to 'user' item.user.id}} 21 | {{item.user.id}} 22 | {{/link-to}} 23 | 24 | 25 | 26 | {{item.time_ago}} 27 | 28 |
29 |
30 | 31 | {{#if item.content}} 32 |
33 | {{item.commentText}} 34 |
35 | {{/if}} 36 | -------------------------------------------------------------------------------- /tests/integration/components/nav-bar-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForComponent, test } from 'ember-qunit'; 2 | import hbs from 'htmlbars-inline-precompile'; 3 | 4 | moduleForComponent('nav-bar', 'Integration | Component | nav bar', { 5 | integration: true 6 | }); 7 | 8 | test('it renders', function(assert) { 9 | // Set any properties with this.set('myProperty', 'value'); 10 | // Handle any actions with this.on('myAction', function(val) { ... }); 11 | 12 | this.render(hbs`{{nav-bar}}`); 13 | 14 | assert.equal(this.$().text().trim(), ''); 15 | 16 | // Template block usage: 17 | this.render(hbs` 18 | {{#nav-bar}} 19 | template block text 20 | {{/nav-bar}} 21 | `); 22 | 23 | assert.equal(this.$().text().trim(), 'template block text'); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/integration/components/card-list-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForComponent, test } from 'ember-qunit'; 2 | import hbs from 'htmlbars-inline-precompile'; 3 | 4 | moduleForComponent('card-list', 'Integration | Component | card list', { 5 | integration: true 6 | }); 7 | 8 | test('it renders', function(assert) { 9 | // Set any properties with this.set('myProperty', 'value'); 10 | // Handle any actions with this.on('myAction', function(val) { ... }); 11 | 12 | this.render(hbs`{{card-list}}`); 13 | 14 | assert.equal(this.$().text().trim(), ''); 15 | 16 | // Template block usage: 17 | this.render(hbs` 18 | {{#card-list}} 19 | template block text 20 | {{/card-list}} 21 | `); 22 | 23 | assert.equal(this.$().text().trim(), 'template block text'); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/integration/components/item-card-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForComponent, test } from 'ember-qunit'; 2 | import hbs from 'htmlbars-inline-precompile'; 3 | 4 | moduleForComponent('item-card', 'Integration | Component | item card', { 5 | integration: true 6 | }); 7 | 8 | test('it renders', function(assert) { 9 | // Set any properties with this.set('myProperty', 'value'); 10 | // Handle any actions with this.on('myAction', function(val) { ... }); 11 | 12 | this.render(hbs`{{item-card}}`); 13 | 14 | assert.equal(this.$().text().trim(), ''); 15 | 16 | // Template block usage: 17 | this.render(hbs` 18 | {{#item-card}} 19 | template block text 20 | {{/item-card}} 21 | `); 22 | 23 | assert.equal(this.$().text().trim(), 'template block text'); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/integration/components/comment-list-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForComponent, test } from 'ember-qunit'; 2 | import hbs from 'htmlbars-inline-precompile'; 3 | 4 | moduleForComponent('comment-list', 'Integration | Component | comment list', { 5 | integration: true 6 | }); 7 | 8 | test('it renders', function(assert) { 9 | // Set any properties with this.set('myProperty', 'value'); 10 | // Handle any actions with this.on('myAction', function(val) { ... }); 11 | 12 | this.render(hbs`{{comment-list}}`); 13 | 14 | assert.equal(this.$().text().trim(), ''); 15 | 16 | // Template block usage: 17 | this.render(hbs` 18 | {{#comment-list}} 19 | template block text 20 | {{/comment-list}} 21 | `); 22 | 23 | assert.equal(this.$().text().trim(), 'template block text'); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/integration/components/item-details-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForComponent, test } from 'ember-qunit'; 2 | import hbs from 'htmlbars-inline-precompile'; 3 | 4 | moduleForComponent('item-details', 'Integration | Component | item details', { 5 | integration: true 6 | }); 7 | 8 | test('it renders', function(assert) { 9 | // Set any properties with this.set('myProperty', 'value'); 10 | // Handle any actions with this.on('myAction', function(val) { ... }); 11 | 12 | this.render(hbs`{{item-details}}`); 13 | 14 | assert.equal(this.$().text().trim(), ''); 15 | 16 | // Template block usage: 17 | this.render(hbs` 18 | {{#item-details}} 19 | template block text 20 | {{/item-details}} 21 | `); 22 | 23 | assert.equal(this.$().text().trim(), 'template block text'); 24 | }); 25 | -------------------------------------------------------------------------------- /tests/integration/components/list-pagination-test.js: -------------------------------------------------------------------------------- 1 | import { moduleForComponent, test } from 'ember-qunit'; 2 | import hbs from 'htmlbars-inline-precompile'; 3 | 4 | moduleForComponent('list-pagination', 'Integration | Component | list pagination', { 5 | integration: true 6 | }); 7 | 8 | test('it renders', function(assert) { 9 | // Set any properties with this.set('myProperty', 'value'); 10 | // Handle any actions with this.on('myAction', function(val) { ... }); 11 | 12 | this.render(hbs`{{list-pagination}}`); 13 | 14 | assert.equal(this.$().text().trim(), ''); 15 | 16 | // Template block usage: 17 | this.render(hbs` 18 | {{#list-pagination}} 19 | template block text 20 | {{/list-pagination}} 21 | `); 22 | 23 | assert.equal(this.$().text().trim(), 'template block text'); 24 | }); 25 | -------------------------------------------------------------------------------- /app/templates/components/item-card.hbs: -------------------------------------------------------------------------------- 1 |
2 | {{item.score}} 3 |
4 | 5 |
6 |
7 | {{#if item.isExternal}} 8 | 9 | {{item.title}} 10 | 11 | 12 | 13 | {{item.domain}} 14 | 15 | {{else}} 16 | {{#link-to 'item' item.id}}{{item.title}}{{/link-to}} 17 | {{/if}} 18 |
19 | 20 | 38 |
39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 mstewart6 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 | -------------------------------------------------------------------------------- /config/deploy.js: -------------------------------------------------------------------------------- 1 | module.exports = function(deployTarget) { 2 | var ENV = { 3 | build: {}, 4 | gzip: { 5 | ignorePattern: '{fastboot/*.js,*.json,vendor*,hnpwa-ember*}', 6 | keep: true, 7 | zopfli: true 8 | }, 9 | s3: { 10 | bucket: 'hnpwa-ember', 11 | region: 'us-east-1' 12 | } 13 | // include other plugin configuration that applies to all deploy targets here 14 | }; 15 | 16 | if(deployTarget === 'development') { 17 | ENV.build.environment = 'development'; 18 | // configure other plugins for development deploy target here 19 | } 20 | 21 | if(deployTarget === 'staging') { 22 | ENV.build.environment = 'production'; 23 | // configure other plugins for staging deploy target here 24 | } 25 | 26 | if(deployTarget === 'production') { 27 | ENV.build.environment = 'production'; 28 | 29 | ENV.s3.accessKeyId = process.env.S3_ACCESS_KEY; 30 | ENV.s3.secretAccessKey = process.env.S3_SECRET; 31 | } 32 | 33 | // Note: if you need to build some configuration asynchronously, you can return 34 | // a promise that resolves with the ENV object instead of returning the 35 | // ENV object synchronously. 36 | return ENV; 37 | }; 38 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HnpwaEmber Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for "body-footer"}} 31 | {{content-for "test-body-footer"}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /ember-cli-build.js: -------------------------------------------------------------------------------- 1 | /* global require, module */ 2 | var EmberApp = require('ember-cli/lib/broccoli/ember-app'); 3 | var env = EmberApp.env() || 'development'; 4 | 5 | module.exports = function(defaults) { 6 | var app = new EmberApp(defaults, { 7 | 'esw-cache-fallback': { 8 | patterns: [ 9 | 'https://node-hnapi.herokuapp.com/(.+)' 10 | ], 11 | }, 12 | 13 | fingerprint: { 14 | generateAssetMap: true, 15 | prepend: (env === 'production') ? 'https://s3.amazonaws.com/hnpwa-ember/' : '/' 16 | } 17 | }); 18 | 19 | // Use `app.import` to add additional libraries to the generated 20 | // output files. 21 | // 22 | // If you need to use different assets in different 23 | // environments, specify an object as the first parameter. That 24 | // object's keys should be the environment name and the values 25 | // should be the asset to use in that environment. 26 | // 27 | // If the library that you are including contains AMD or ES6 28 | // modules that you would like to import into your application 29 | // please specify an object with the list of modules as keys 30 | // along with the exports of each module as its value. 31 | 32 | return app.toTree(); 33 | }; 34 | -------------------------------------------------------------------------------- /app/models/item.js: -------------------------------------------------------------------------------- 1 | import Model from 'ember-data/model'; 2 | import attr from 'ember-data/attr'; 3 | import { hasMany, belongsTo } from 'ember-data/relationships'; 4 | import Ember from 'ember'; 5 | 6 | export default Model.extend({ 7 | comments_count: attr('number'), 8 | content: attr('string'), 9 | domain: attr('string'), 10 | points: attr('number'), 11 | time_ago: attr('string'), 12 | title: attr('string'), 13 | type: attr('string'), 14 | url: attr('string'), 15 | 16 | user: belongsTo(), 17 | comments: hasMany('comment', { inverse: null, async: false }), 18 | 19 | isExternal: Ember.computed('domain', function() { 20 | return !Ember.isNone(this.get('domain')); 21 | }), 22 | 23 | score: Ember.computed('points', function() { 24 | return this.get('points') || 0; 25 | }), 26 | 27 | commentText: Ember.computed('content', function() { 28 | // FIXME: this is super unsafe 29 | return Ember.String.htmlSafe(this.get('content')); 30 | }), 31 | 32 | hasChildren: Ember.computed('comments', function() { 33 | return this.get('comments.length') > 0; 34 | }), 35 | 36 | activeChildren: Ember.computed('comments.@each.deleted', function() { 37 | return this.get('comments').reject((comment) => { 38 | return comment.get('deleted'); 39 | }); 40 | }) 41 | }); 42 | -------------------------------------------------------------------------------- /config/environment.js: -------------------------------------------------------------------------------- 1 | /* global module */ 2 | 3 | module.exports = function(environment) { 4 | var ENV = { 5 | modulePrefix: 'hnpwa-ember', 6 | environment: environment, 7 | rootURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | } 14 | }, 15 | 16 | APP: { 17 | // Here you can pass flags/options to your application instance 18 | // when it is created 19 | }, 20 | 21 | apiUrl: 'https://node-hnapi.herokuapp.com' 22 | }; 23 | 24 | if(environment === 'development') { 25 | // ENV.APP.LOG_RESOLVER = true; 26 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 27 | // ENV.APP.LOG_TRANSITIONS = true; 28 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 29 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 30 | } 31 | 32 | if(environment === 'test') { 33 | // Testem prefers this... 34 | ENV.locationType = 'none'; 35 | 36 | // keep test console output quieter 37 | ENV.APP.LOG_ACTIVE_GENERATION = false; 38 | ENV.APP.LOG_VIEW_LOOKUPS = false; 39 | 40 | ENV.APP.rootElement = '#ember-testing'; 41 | } 42 | 43 | if(environment === 'production') { 44 | } 45 | 46 | return ENV; 47 | }; 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hnpwa-ember 2 | 3 | This README outlines the details of collaborating on this Ember application. 4 | A short introduction of this app could easily go here. 5 | 6 | ## Prerequisites 7 | 8 | You will need the following things properly installed on your computer. 9 | 10 | * [Git](http://git-scm.com/) 11 | * [Node.js](http://nodejs.org/) (with NPM) 12 | * [Bower](http://bower.io/) 13 | * [Ember CLI](http://ember-cli.com/) 14 | * [PhantomJS](http://phantomjs.org/) 15 | 16 | ## Installation 17 | 18 | * `git clone ` this repository 19 | * change into the new directory 20 | * `npm install` 21 | * `bower install` 22 | 23 | ## Running / Development 24 | 25 | * `ember serve` 26 | * Visit your app at [http://localhost:4200](http://localhost:4200). 27 | 28 | ### Code Generators 29 | 30 | Make use of the many generators for code, try `ember help generate` for more details 31 | 32 | ### Running Tests 33 | 34 | * `ember test` 35 | * `ember test --server` 36 | 37 | ### Building 38 | 39 | * `ember build` (development) 40 | * `ember build --environment production` (production) 41 | 42 | ### Deploying 43 | 44 | Specify what it takes to deploy your app. 45 | 46 | ## Further Reading / Useful Links 47 | 48 | * [ember.js](http://emberjs.com/) 49 | * [ember-cli](http://ember-cli.com/) 50 | * Development Browser Extensions 51 | * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 52 | * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 53 | 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hnpwa-ember", 3 | "version": "0.0.0", 4 | "description": "Small description for hnpwa-ember goes here", 5 | "private": true, 6 | "directories": { 7 | "doc": "doc", 8 | "test": "tests" 9 | }, 10 | "scripts": { 11 | "build": "ember build", 12 | "start": "ember server", 13 | "test": "ember test" 14 | }, 15 | "repository": "", 16 | "engines": { 17 | "node": ">=6.0.0" 18 | }, 19 | "author": "", 20 | "license": "MIT", 21 | "devDependencies": { 22 | "ember-cli-deploy": "^1.0.0", 23 | "ember-cli-deploy-gzip": "^1.0.0", 24 | "ember-cli-inject-live-reload": "^1.4.0" 25 | }, 26 | "dependencies": { 27 | "babel-eslint": "^7.2.3", 28 | "broccoli-asset-rev": "^2.5.0", 29 | "ember-ajax": "^2.0.1", 30 | "ember-cli": "2.12.2", 31 | "ember-cli-app-version": "^1.0.0", 32 | "ember-cli-babel": "^5.1.6", 33 | "ember-cli-dependency-checker": "^1.2.0", 34 | "ember-cli-deploy-build": "^1.1.0", 35 | "ember-cli-deploy-manifest": "^1.1.0", 36 | "ember-cli-deploy-s3": "^1.1.0", 37 | "ember-cli-dotenv": "^1.2.0", 38 | "ember-cli-eslint": "^3.1.0", 39 | "ember-cli-fastboot": "^1.0.0-rc.1", 40 | "ember-cli-htmlbars": "^1.0.3", 41 | "ember-cli-htmlbars-inline-precompile": "^0.3.1", 42 | "ember-cli-moment-shim": "^3.3.0", 43 | "ember-cli-one-script": "0.0.1", 44 | "ember-cli-qunit": "^4.0.0", 45 | "ember-cli-release": "^0.2.9", 46 | "ember-cli-sass": "^6.2.0", 47 | "ember-cli-sri": "^2.1.0", 48 | "ember-cli-test-loader": "^1.1.0", 49 | "ember-cli-uglify": "^1.2.0", 50 | "ember-data": "2.12.2", 51 | "ember-export-application-global": "^1.0.5", 52 | "ember-fetch": "github:stefanpenner/ember-fetch#994c0df4183185a812e0b096f79af3f26ed71937", 53 | "ember-load-initializers": "^0.5.1", 54 | "ember-moment": "6.0.0", 55 | "ember-qunit": "^2.1.3", 56 | "ember-resolver": "^2.0.3", 57 | "ember-service-worker": "^0.6.6", 58 | "ember-service-worker-asset-cache": "^0.6.1", 59 | "ember-service-worker-cache-fallback": "^0.6.1", 60 | "ember-service-worker-index": "^0.6.1", 61 | "ember-truth-helpers": "^1.3.0", 62 | "ember-web-app": "^1.2.0", 63 | "ember-web-app-rename": "^1.0.0", 64 | "eslint": "^3.19.0", 65 | "loader.js": "^4.0.1", 66 | "moment": "^2.18.1", 67 | "node-zopfli": "^2.0.2" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | HnpwaEmber 7 | 8 | 9 | 10 | 11 | {{content-for "head"}} 12 | 13 | 16 | 17 | {{content-for "head-footer"}} 18 | 19 | 20 | {{content-for "body"}} 21 | 22 | 25 | 26 | 27 | 28 | 31 | 32 | {{content-for "body-footer"}} 33 | 34 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: 'eslint:recommended', 4 | parser: 'babel-eslint', 5 | parserOptions: { 6 | 'ecmaVersion': 2017, 7 | 'sourceType': 'module' 8 | }, 9 | globals: { 10 | '_': true, 11 | 'server': true 12 | }, 13 | env: { 14 | 'browser': true, 15 | 'jquery' : true 16 | }, 17 | rules: { 18 | // Possible Errors 19 | 'no-unsafe-finally': 'error', 20 | 'no-cond-assign': ['error', 'always'], 21 | 'valid-jsdoc': 'error', 22 | 23 | // Best Practices 24 | 'accessor-pairs': 'error', 25 | 'array-callback-return': 'error', 26 | 'block-scoped-var': 'error', 27 | 'curly': ['error', 'multi-line'], 28 | 'dot-location': ['error', 'property'], 29 | 'dot-notation': 'error', 30 | 'eqeqeq': 'error', 31 | 'guard-for-in': 'error', 32 | 'no-case-declarations': 'error', 33 | 'no-div-regex': 'error', 34 | 'no-extend-native': 'error', 35 | 'no-extra-bind': 'error', 36 | 'no-extra-label': 'error', 37 | 'no-floating-decimal': 'error', 38 | 'no-implicit-coercion': 'error', 39 | 'no-implicit-globals': 'error', 40 | 'no-iterator': 'error', 41 | 'no-labels': 'error', 42 | 'no-lone-blocks': 'error', 43 | 'no-loop-func': 'error', 44 | 'no-multi-str': 'error', 45 | 'no-native-reassign': 'error', 46 | 'no-new': 'error', 47 | 'no-new-func': 'error', 48 | 'no-new-wrappers': 'error', 49 | 'no-octal-escape': 'error', 50 | 'no-proto': 'error', 51 | 'no-return-assign': 'error', 52 | 'no-self-compare': 'error', 53 | 'no-sequences': 'error', 54 | 'no-throw-literal': 'error', 55 | 'no-unmodified-loop-condition': 'error', 56 | 'no-unused-expressions': 'error', 57 | 'no-useless-call': 'error', 58 | 'no-useless-concat': 'error', 59 | 'no-useless-escape': 'error', 60 | 'no-void': 'error', 61 | 'no-with': 'error', 62 | 'radix': 'error', 63 | 'vars-on-top': 'error', 64 | 'no-catch-shadow': 'error', 65 | 'no-undef-init': 'error', 66 | 'no-use-before-define': 'error', 67 | 'yoda': ['error', 'never'], 68 | // Stylistic Issues 69 | 'array-bracket-spacing': 'error', 70 | 'block-spacing': 'error', 71 | 'brace-style': 'error', 72 | 'camelcase': ['error', {'properties': 'never'}], 73 | 'comma-spacing': 'error', 74 | 'comma-style': 'error', 75 | 'computed-property-spacing': 'error', 76 | 'consistent-this': 'error', 77 | 'eol-last': 'error', 78 | 'func-style': ['error', 'declaration', {'allowArrowFunctions': true}], 79 | 'indent': ['error', 2, { 'VariableDeclarator': {'var': 2, 'let': 2, 'const': 3}, 'SwitchCase': 1 }], 80 | 'key-spacing': 'error', 81 | 'keyword-spacing': [ 82 | 'error', 83 | { 84 | 'overrides': { 85 | 'if': {'after': false}, 86 | 'for': {'after': false}, 87 | 'while': {'after': false}, 88 | 'switch': {'after': false} 89 | } 90 | } 91 | ], 92 | 'linebreak-style': 'error', 93 | 'max-len': ['error', 160], 94 | 'max-statements-per-line': ['error', {'max': 1}], 95 | 'new-cap': 'error', 96 | 'new-parens': 'error', 97 | 'no-array-constructor': 'error', 98 | 'no-inline-comments': 'error', 99 | 'no-lonely-if': 'error', 100 | 'no-mixed-spaces-and-tabs': 'error', 101 | 'no-multiple-empty-lines': 'error', 102 | 'no-nested-ternary': 'error', 103 | 'no-new-object': 'error', 104 | 'no-spaced-func': 'error', 105 | 'no-trailing-spaces': 'error', 106 | 'no-underscore-dangle': ['error', {'allowAfterThis': true}], 107 | 'no-unneeded-ternary': 'error', 108 | 'no-whitespace-before-property': 'error', 109 | 'no-unused-vars': [ 'error', { 'argsIgnorePattern': '^_' } ], 110 | 'object-curly-spacing': ['error', 'always'], 111 | 'one-var': ['error', {'let': 'always', 'const': 'always'}], 112 | 'one-var-declaration-per-line': 'error', 113 | 'operator-assignment': 'error', 114 | 'operator-linebreak': 'error', 115 | 'padded-blocks': ['error', 'never'], 116 | 'quote-props': ['error', 'as-needed'], 117 | 'quotes': ['error', 'single', {'avoidEscape': true}], 118 | 'radix': ['error', 'always'], 119 | 'semi': 'error', 120 | 'semi-spacing': 'error', 121 | 'space-before-blocks': 'error', 122 | 'space-before-function-paren': ['error', 'never'], 123 | 'space-infix-ops': 'error', 124 | 'space-unary-ops': 'error', 125 | 'spaced-comment': 'error', 126 | 'space-in-parens': ['error', 'never'] 127 | } 128 | }; 129 | --------------------------------------------------------------------------------