diff --git a/package.json b/package.json index fc352ee..0c2eb78 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "reflect-metadata": "^0.1.0", "rtts_assert": "2.0.0-alpha.34", "rx": "^2.5.3", + "firebase": "^2.2.4", "zone.js": "^0.5.2" }, "devDependencies": { @@ -68,6 +69,7 @@ "url-loader": "^0.5.5", "webpack": "^1.10.5", "webpack-dev-server": "^1.10.1", - "webpack-stream": "^2.1.0" + "webpack-stream": "^2.1.0", + "moment": "^2.10.6" } } diff --git a/src/app/components/example-modules/example-modules.ts b/src/app/components/example-modules/example-modules.ts index 2876b5b..c023a34 100644 --- a/src/app/components/example-modules/example-modules.ts +++ b/src/app/components/example-modules/example-modules.ts @@ -7,6 +7,7 @@ import {RouteConfig, routerDirectives} from 'angular2/router'; import {Tictactoe} from './tictactoe-module/tictactoe'; import {Search} from './search-module/search'; import {Memory} from './memory-module/memory'; +import {HackerNews} from './hn-module/hn'; // View let template = require('./example-modules.html'); let styles = require('./example-modules.css') @@ -17,7 +18,8 @@ let styles = require('./example-modules.css') { path: '/', redirectTo: '/search' }, { path: '/search', as: 'search', component: Search }, { path: '/tictactoe', as: 'tictactoe', component: Tictactoe }, - { path: '/memory', as: 'memory', component: Memory } + { path: '/memory', as: 'memory', component: Memory }, + { pathL '/hn', as: 'hn', component: HackerNews } ]) @View({ directives: [ routerDirectives, CSSClass ], diff --git a/src/app/components/example-modules/hn-module/components/hn-item/hn-item.ts b/src/app/components/example-modules/hn-module/components/hn-item/hn-item.ts new file mode 100644 index 0000000..ea157d8 --- /dev/null +++ b/src/app/components/example-modules/hn-module/components/hn-item/hn-item.ts @@ -0,0 +1,70 @@ +import { Component, View, For, If, Switch, SwitchWhen, SwitchDefault } from 'angular2/angular2'; +import { HNApi } from '../../services/hn-api'; +import { timeAgo } from '../../../../../services/time'; +import { DomainPipe } from './pipes/domain.pipe'; + +let view = require('./views/hn-item.html'); +let styles = require('./styles/hn-item.less'); + +var hnApi; + +@Component({ + selector: 'hn-item', + viewBindings: [HNApi], + properties: { + newItemId: 'itemId', + newLoadChildren: 'loadChildren', + newTopLevel: 'topLevel' + } +}) +@View({ + template: view, + styles: [ styles ], + directives: [ + For, + If, + Switch, + SwitchWhen, + SwitchDefault + ] +}) +export class HNItem { + data: Object; + domainPipe: Object; + loadChildren: boolean; + topLevel: boolean; + itemId; + timeAgo; + + + constructor(hnApiInstance: HNApi) { + this.domainPipe = DomainPipe.transform; + + // Default value. + this.loadChildren = true; + + // Make accessible in other methods. + hnApi = hnApiInstance; + + this.timeAgo = timeAgo; + } + + set newItemId(itemId) { + this.itemId = itemId; + this.fetchData(); + } + + set newLoadChildren(loadChildren) { + this.loadChildren = loadChildren === 'true'; + } + + set newTopLevel(topLevel) { + this.topLevel = topLevel === 'true'; + } + + fetchData() { + hnApi.fetchItem(this.itemId).then(data => { + this.data = data; + }); + } +} diff --git a/src/app/components/example-modules/hn-module/components/hn-item/pipes/domain.ts b/src/app/components/example-modules/hn-module/components/hn-item/pipes/domain.ts new file mode 100644 index 0000000..e3110fb --- /dev/null +++ b/src/app/components/example-modules/hn-module/components/hn-item/pipes/domain.ts @@ -0,0 +1,11 @@ +import { Pipes } from 'angular2/angular2'; + +export class DomainPipe extends Pipes { + static transform(input) { + if (!input) { + return ''; + } + var domain = input.split('/')[2]; + return domain ? domain.replace('www.', '') : domain; + } +} diff --git a/src/app/components/example-modules/hn-module/components/hn-item/styles/hn-item.less b/src/app/components/example-modules/hn-module/components/hn-item/styles/hn-item.less new file mode 100644 index 0000000..08e5b2e --- /dev/null +++ b/src/app/components/example-modules/hn-module/components/hn-item/styles/hn-item.less @@ -0,0 +1,33 @@ +hn-item { + display: block; + margin: 10px 0; +} + +.hnItem-title { + font-size: 10pt; + color: #828282; +} + +.hnItem > section { + font-size: 7pt; + &, + a:link, + a:visited { + color: #828282; + } + a:hover { + text-decoration: underline; + } +} + +.hnItem--comment { + margin: 15px 0; +} + +.hnItem--coment-content { + margin-top: 5px; +} + +.hnItem--comment-children { + margin-left: 50px; +} diff --git a/src/app/components/example-modules/hn-module/components/hn-item/views/hn-item.html b/src/app/components/example-modules/hn-module/components/hn-item/views/hn-item.html new file mode 100644 index 0000000..d893a78 --- /dev/null +++ b/src/app/components/example-modules/hn-module/components/hn-item/views/hn-item.html @@ -0,0 +1,69 @@ +
+
+
+
+
+
+
+
+
+ + + + +
+
diff --git a/src/app/components/example-modules/hn-module/directives/home.ts b/src/app/components/example-modules/hn-module/directives/home.ts new file mode 100644 index 0000000..86512d5 --- /dev/null +++ b/src/app/components/example-modules/hn-module/directives/home.ts @@ -0,0 +1,28 @@ +import { Component, View, For } from 'angular2/angular2'; +import { HNApi } from '../services/hn-api'; +import { HNItem } from '../components/hn-item/hn-item'; + +let view = require('../views/home.html'); +let styles = require('../styles/hn.less'); + +@Component({ + selector: 'page-home', + viewBindings: HNApi +}) +@View({ + directives: [ + For, + HNItem + ], + styles: [ styles ], + template: view +}) +export class HomePage { + topStories: Array; + + constructor(public hnApi: HNApi) { + hnApi.fetchTopStories().then(() => { + this.topStories = hnApi.topStories; + }); + } +} diff --git a/src/app/components/example-modules/hn-module/directives/item.ts b/src/app/components/example-modules/hn-module/directives/item.ts new file mode 100644 index 0000000..e4494a0 --- /dev/null +++ b/src/app/components/example-modules/hn-module/directives/item.ts @@ -0,0 +1,27 @@ +import { Component, View, For } from 'angular2/angular2'; +import { HNApi } from '../services/hn-api'; +import { HNItem } from '../../components/hn-item'; + +let view = require('../views/item.html'); +let styles = require('../styles/hn.less'); + +@Component({ + selector: 'page-item', + viewBindings: HNApi +}) +@View({ + directives: [ + For, + HNItem + ], + template: view, + styles: [ styles ] +}) +export class ItemPage { + childrenIds: Array; + itemId; + + constructor(public hnApi: HNApi, id) { + this.itemId = id; + } +} diff --git a/src/app/components/example-modules/hn-module/directives/user.ts b/src/app/components/example-modules/hn-module/directives/user.ts new file mode 100644 index 0000000..9da63f9 --- /dev/null +++ b/src/app/components/example-modules/hn-module/directives/user.ts @@ -0,0 +1,30 @@ +import { Component, View, For, If } from 'angular2/angular2'; +import { HNApi } from '../services/hn-api'; +import { timeAgo } from '../../../../../services/time'; +import { HNItem } from '../components/hn-item/hn-item'; + +let view = require('../views/user.html'); +let styles = require('../styles/hn.less'); + +@Component({ + selector: 'page-user', + viewBindings: HNApi +}) +@View({ + directives: [ + For, + If, + HNItem + ], + styles: [ styles ], + template: view +}) +export class UserPage { + showSubmissions: boolean; + timeAgo; + + constructor(hnApi: HNApi) { + this.timeAgo = timeAgo; + this.showSubmissions = false; + } +} diff --git a/src/app/components/example-modules/hn-module/hn.ts b/src/app/components/example-modules/hn-module/hn.ts new file mode 100644 index 0000000..0292b84 --- /dev/null +++ b/src/app/components/example-modules/hn-module/hn.ts @@ -0,0 +1,35 @@ +//import {zone} from 'zone.js'; +//window.zone = window.Zone = zone; + +let styles = require('./styles/hn.less'); +let view = require('/.views/hn.html'); + +import {RouteConfig} from 'angular2/router'; +import {Component, View} from 'angular2/angular2'; +import {HomePage} from './directives/home' +import {ItemPage} from './directives/item' +import {UserPage} from './directives/user' +import {HNItem} from './components/hn-item/hn-item'; + + +@Component({ + selector: 'hacker-news' +}) +@RouteConfig([ + { path: '/', redirectTo: '/hn' }, + { path: '/hn', as: 'hn', component: HackerNews }, + { path: '/hn-item', as: 'hn-item', component: HNItem }, +]) +@View({ + template: view, + styles: [ styles ], + directives: [ + HomePage, + ItemPage, + UserPage + ] +}) +export class HackerNews { + +} + diff --git a/src/app/components/example-modules/hn-module/services/hn-api.ts b/src/app/components/example-modules/hn-module/services/hn-api.ts new file mode 100644 index 0000000..d2650d5 --- /dev/null +++ b/src/app/components/example-modules/hn-module/services/hn-api.ts @@ -0,0 +1,78 @@ +import {Firebase} from 'firebase/firebase'; +import {Injectable} from 'angular2/angular2'; + +const connection = new Firebase('https://hacker-news.firebaseio.com/v0/'); + +@Injectable() +export class HNApi { + itemStore: Object; + userStore: Object; + topStories: Array; + + constructor() { + this.itemStore = {}; + this.userStore = {}; + } + + fetchTopStories() { + return new Promise((resolve) => { + HNApi.topStoriesRef().once('value', snapshot => { + this.topStories = snapshot.val().splice(0, 20); + + resolve(this.topStories); + }); + }); + } + + fetchItems(items = []) { + return new Promise(resolve => { + let promises = []; + + items.forEach(itemId => { + promises.push(new Promise((resolveItem) => { + HNApi.itemRef(itemId).on('value', value => { + this.itemStore[itemId] = value.val(); + + resolveItem(this.itemStore[itemId]); + }); + })); + }); + + Promise.all(promises).then(resolve); + }); + } + + fetchItem(item) { + if (!item) { + return Promise.reject(item); + } + + return this.fetchItems([item]).then(data => data[0]); + } + + fetchUser(userId) { + if (!userId) { + return Promise.reject(userId); + } + + return new Promise(resolve => { + HNApi.userRef(userId).on('value', value => { + this.userStore[userId] = value.val(); + + resolve(this.userStore[userId]); + }); + }); + } + + static topStoriesRef() { + return connection.child('topstories/'); + } + + static itemRef(itemId) { + return connection.child('item/' + itemId); + } + + static userRef(userId) { + return connection.child('user/' + userId); + } +} diff --git a/src/app/components/example-modules/hn-module/styles/hn.less b/src/app/components/example-modules/hn-module/styles/hn.less new file mode 100644 index 0000000..72aff87 --- /dev/null +++ b/src/app/components/example-modules/hn-module/styles/hn.less @@ -0,0 +1,115 @@ +input { font-family:Courier; font-size:10pt; color:#000000; } +input[type=submit] { font-family:Verdana, Geneva, sans-serif; } +textarea { font-family:Courier; font-size:10pt; color:#000000; } + +a:link { color:#000000; text-decoration:none; } +a:visited { color:#828282; text-decoration:none; } + +.default { font-family:Verdana, Geneva, sans-serif; font-size: 10pt; color:#828282; } +.admin { font-family:Verdana, Geneva, sans-serif; font-size:8.5pt; color:#000000; } + +.adtitle { font-family:Verdana, Geneva, sans-serif; font-size: 9pt; color:#828282; } +.yclinks { font-family:Verdana, Geneva, sans-serif; font-size: 8pt; color:#828282; } +.pagetop { font-family:Verdana, Geneva, sans-serif; font-size: 10pt; color:#222222; } +.comhead { font-family:Verdana, Geneva, sans-serif; font-size: 8pt; color:#828282; } +.comment { font-family:Verdana, Geneva, sans-serif; font-size: 9pt; } +.dead { font-family:Verdana, Geneva, sans-serif; font-size: 9pt; color:#dddddd; } + +.comment a:link, .comment a:visited { text-decoration:underline;} +.dead a:link, .dead a:visited { color:#dddddd; } +.pagetop a:visited { color:#000000;} +.topsel a:link, .topsel a:visited { color:#ffffff; } + +.comhead a:link, .subtext a:visited { color:#828282; } +.comhead a:hover { text-decoration:underline; } + +.default p { margin-top: 8px; margin-bottom: 0px; } + +.pagebreak {page-break-before:always} + +pre { overflow: auto; padding: 2px; max-width:600px; } +pre:hover {overflow:auto} + + +/** + * Real Styles + */ + +.u-pointer { + cursor: pointer; +} + +body { + font-family: Verdana, Geneva, sans-serif; + font-size: 10pt; + color: #828282; +} + +.bodyContainer { + margin: 0 auto; + width: 85%; + max-width: 1280px; + background: #F6F6EF; +} + +.headerBar { + background: #FF6600; +} + +.userDetail { + margin: 10px 20px; +} + +.userDetail-item { + + &:first-of-type { + margin-top: 30px; + } +} + +.itemDetail { + margin: 10px 20px; +} + +.itemDetail-item { + + &:first-of-type { + margin-top: 30px; + } +} + +hn-item { + display: block; + margin: 10px 0; +} + +.hnItem-title { + font-size: 10pt; + color:#828282; +} + +.hnItem > section { + font-size: 7pt; + + &, + a:link, + a:visited { + color: #828282; + } + + a:hover { + text-decoration: underline; + } +} + +.hnItem--comment { + margin: 15px 0; +} + +.hnItem--coment-content { + margin-top: 5px; +} + +.hnItem--comment-children { + margin-left: 50px; +} \ No newline at end of file diff --git a/src/app/components/example-modules/hn-module/views/footer-bar.html b/src/app/components/example-modules/hn-module/views/footer-bar.html new file mode 100644 index 0000000..cc139c9 --- /dev/null +++ b/src/app/components/example-modules/hn-module/views/footer-bar.html @@ -0,0 +1,10 @@ +
+ + + + + + + +
+
diff --git a/src/app/components/example-modules/hn-module/views/header-bar.html b/src/app/components/example-modules/hn-module/views/header-bar.html new file mode 100644 index 0000000..29b8a38 --- /dev/null +++ b/src/app/components/example-modules/hn-module/views/header-bar.html @@ -0,0 +1,19 @@ +
+ + + + + + + +
+ + + + + + Hacker News written in Angular 2 + + +
+
diff --git a/src/app/components/example-modules/hn-module/views/hn.html b/src/app/components/example-modules/hn-module/views/hn.html new file mode 100644 index 0000000..e34488b --- /dev/null +++ b/src/app/components/example-modules/hn-module/views/hn.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/app/components/example-modules/hn-module/views/home.html b/src/app/components/example-modules/hn-module/views/home.html new file mode 100644 index 0000000..2792aac --- /dev/null +++ b/src/app/components/example-modules/hn-module/views/home.html @@ -0,0 +1,7 @@ +
+
    +
  1. + +
  2. +
+
diff --git a/src/app/components/example-modules/hn-module/views/item.html b/src/app/components/example-modules/hn-module/views/item.html new file mode 100644 index 0000000..be651b7 --- /dev/null +++ b/src/app/components/example-modules/hn-module/views/item.html @@ -0,0 +1,6 @@ +
+ +
+ +
+
diff --git a/src/app/components/example-modules/hn-module/views/user.html b/src/app/components/example-modules/hn-module/views/user.html new file mode 100644 index 0000000..47ffb2d --- /dev/null +++ b/src/app/components/example-modules/hn-module/views/user.html @@ -0,0 +1,18 @@ +
+

user: {{data.id}}

+

created: {{timeAgo(data.created)}}

+

karma: {{data.karma}}

+

about: {{data.about}}

+

+ show submitted stories and comments +

+
+ +
+
+

+

+ submissions + comments +

+
diff --git a/src/app/components/example-modules/search-module/services/github.ts b/src/app/components/example-modules/search-module/services/github.ts index 890338c..4ccd7ae 100644 --- a/src/app/components/example-modules/search-module/services/github.ts +++ b/src/app/components/example-modules/search-module/services/github.ts @@ -25,7 +25,6 @@ export class Github implements ISearchable { } - export var githubInjectables = [ bind(Github).toClass(Github) ]; diff --git a/src/app/services/time.ts b/src/app/services/time.ts new file mode 100644 index 0000000..ff789c8 --- /dev/null +++ b/src/app/services/time.ts @@ -0,0 +1,5 @@ +import {moment} from 'moment/moment'; + +export function timeAgo(time) { + return moment(time * 1000).fromNow(); +}