├── src ├── assets │ ├── .gitkeep │ └── .npmignore ├── app │ ├── component │ │ ├── app.component.css │ │ ├── index │ │ │ ├── index.component.css │ │ │ ├── index.component.html │ │ │ ├── index.component.ts │ │ │ └── index.component.spec.ts │ │ ├── content │ │ │ ├── content.component.css │ │ │ ├── content.component.html │ │ │ ├── content.component.spec.ts │ │ │ └── content.component.ts │ │ ├── app.component.html │ │ ├── top │ │ │ ├── top.component.css │ │ │ ├── top.component.html │ │ │ ├── top.component.spec.ts │ │ │ └── top.component.ts │ │ ├── topic │ │ │ ├── topic.component.css │ │ │ ├── topic.component.html │ │ │ ├── topic.component.spec.ts │ │ │ └── topic.component.ts │ │ ├── app.component.ts │ │ ├── head │ │ │ ├── head.component.spec.ts │ │ │ ├── head.component.html │ │ │ ├── head.component.css │ │ │ └── head.component.ts │ │ ├── reply │ │ │ ├── reply.component.spec.ts │ │ │ ├── reply.component.ts │ │ │ ├── reply.component.html │ │ │ └── reply.component.css │ │ └── content-detail │ │ │ ├── content-detail.component.spec.ts │ │ │ ├── content-detail.component.ts │ │ │ ├── content-detail.component.html │ │ │ └── content-detail.component.css │ ├── class │ │ ├── tab.ts │ │ ├── param.ts │ │ └── topic.ts │ ├── index.ts │ ├── util │ │ ├── util.ts │ │ ├── event-bus.ts │ │ └── dom.ts │ ├── pipe │ │ ├── interval.pipe.spec.ts │ │ └── interval.pipe.ts │ ├── router │ │ └── app.routing.ts │ ├── service │ │ ├── store.service.ts │ │ └── topic.service.ts │ └── app.module.ts ├── favicon.ico ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── typings.d.ts ├── main.ts ├── tsconfig.json ├── index.html ├── markdown.css ├── polyfills.ts ├── styles.css └── test.ts ├── e2e ├── app.po.ts ├── app.e2e-spec.ts └── tsconfig.json ├── .editorconfig ├── .gitignore ├── protractor.conf.js ├── karma.conf.js ├── angular-cli.json ├── package.json ├── README.md └── tslint.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/.npmignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/component/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/component/index/index.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/component/content/content.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/class/tab.ts: -------------------------------------------------------------------------------- 1 | export class Tab { 2 | text: string; 3 | val: string; 4 | } -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cacivy/angular-cnode/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/app/component/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/component/index/index.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/index.ts: -------------------------------------------------------------------------------- 1 | export * from './component/app.component'; 2 | export * from './app.module'; 3 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/app/component/top/top.component.css: -------------------------------------------------------------------------------- 1 | .fixedIcon { 2 | position: fixed; 3 | right: 10%; 4 | bottom: 10%; 5 | opacity: 0.7; 6 | } -------------------------------------------------------------------------------- /src/app/component/top/top.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/component/content/content.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | // Typings reference file, see links for more information 2 | // https://github.com/typings/typings 3 | // https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html 4 | 5 | declare var System: any; 6 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class CnodePage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/component/topic/topic.component.css: -------------------------------------------------------------------------------- 1 | .reply-bar { 2 | background: #f6f6f6; 3 | padding: 5px 15px; 4 | } 5 | .topic { 6 | position: relative; 7 | animation:slide .1s ease-in; 8 | } 9 | 10 | @keyframes slide { 11 | from {left: 100%} 12 | to {left: 0} 13 | } -------------------------------------------------------------------------------- /src/app/component/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'app works!'; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/util/util.ts: -------------------------------------------------------------------------------- 1 | export const Util = { 2 | convertParamToString : (param: Object) => { 3 | let quertString: string = '' 4 | 5 | for (let key in param) { 6 | quertString += `&${key}=${param[key]}` 7 | } 8 | 9 | return quertString.substr(1) 10 | } 11 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = tab 7 | indent_size = 4 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | max_line_length = 0 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /src/app/component/index/index.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-index', 5 | templateUrl: './index.component.html', 6 | styleUrls: ['./index.component.css'] 7 | }) 8 | export class IndexComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/pipe/interval.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { IntervalPipe } from './interval.pipe'; 5 | 6 | describe('Pipe: Interval', () => { 7 | it('create an instance', () => { 8 | let pipe = new IntervalPipe(); 9 | expect(pipe).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { CnodePage } from './app.po'; 2 | 3 | describe('cnode App', function() { 4 | let page: CnodePage; 5 | 6 | beforeEach(() => { 7 | page = new CnodePage(); 8 | }); 9 | 10 | it('should display message saying app works', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('app works!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/app/class/param.ts: -------------------------------------------------------------------------------- 1 | export class TopicParam { 2 | constructor(page: number = 1, tab: string = '', limit: number = 20, mdrender: boolean = true) { 3 | this.page = page 4 | this.tab = tab 5 | this.limit = limit 6 | this.mdrender = mdrender 7 | } 8 | page: number 9 | tab: string 10 | limit: number 11 | mdrender: boolean 12 | } -------------------------------------------------------------------------------- /src/app/component/top/top.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { TopComponent } from './top.component'; 5 | 6 | describe('Component: Top', () => { 7 | it('should create an instance', () => { 8 | let component = new TopComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/component/head/head.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { HeadComponent } from './head.component'; 5 | 6 | describe('Component: Head', () => { 7 | it('should create an instance', () => { 8 | let component = new HeadComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/component/topic/topic.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
{{ topic.replies && topic.replies.length ? '评论' : '暂无评论'}}
5 | 8 | 9 |
-------------------------------------------------------------------------------- /src/app/component/index/index.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { IndexComponent } from './index.component'; 5 | 6 | describe('Component: Index', () => { 7 | it('should create an instance', () => { 8 | let component = new IndexComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/component/reply/reply.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { ReplyComponent } from './reply.component'; 5 | 6 | describe('Component: Reply', () => { 7 | it('should create an instance', () => { 8 | let component = new ReplyComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/component/topic/topic.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { TopicComponent } from './topic.component'; 5 | 6 | describe('Component: Topic', () => { 7 | it('should create an instance', () => { 8 | let component = new TopicComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { enableProdMode } from '@angular/core'; 5 | import { environment } from './environments/environment'; 6 | import { AppModule } from './app/'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule); 13 | -------------------------------------------------------------------------------- /src/app/component/content/content.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { ContentComponent } from './content.component'; 5 | 6 | describe('Component: Content', () => { 7 | it('should create an instance', () => { 8 | let component = new ContentComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "outDir": "../dist/out-tsc-e2e", 10 | "sourceMap": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "../node_modules/@types" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/app/component/content-detail/content-detail.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { ContentDetailComponent } from './content-detail.component'; 5 | 6 | describe('Component: ContentDetail', () => { 7 | it('should create an instance', () => { 8 | let component = new ContentDetailComponent(); 9 | expect(component).toBeTruthy(); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/component/head/head.component.html: -------------------------------------------------------------------------------- 1 |
2 | 11 |
12 |
-------------------------------------------------------------------------------- /src/app/component/reply/reply.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | import {Reply} from '../../class/topic' 4 | 5 | @Component({ 6 | selector: 'app-reply', 7 | templateUrl: './reply.component.html', 8 | styleUrls: ['./reply.component.css'] 9 | }) 10 | export class ReplyComponent implements OnInit { 11 | 12 | constructor() { } 13 | 14 | @Input() 15 | reply: Reply 16 | 17 | ngOnInit() { 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/app/component/reply/reply.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | {{reply.author.loginname}} 5 |
6 |
7 |
8 |
9 |
10 | 11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": false, 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "lib": [ 7 | "es6", 8 | "dom" 9 | ], 10 | "mapRoot": "./", 11 | "module": "es6", 12 | "moduleResolution": "node", 13 | "outDir": "../dist/out-tsc", 14 | "sourceMap": true, 15 | "target": "es5", 16 | "typeRoots": [ 17 | "../node_modules/@types" 18 | ] 19 | } 20 | } -------------------------------------------------------------------------------- /.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 | # IDEs and editors 12 | /.idea 13 | /.vscode 14 | .project 15 | .classpath 16 | *.launch 17 | .settings/ 18 | 19 | # misc 20 | /.sass-cache 21 | /connect.lock 22 | /coverage/* 23 | /libpeerconnection.log 24 | npm-debug.log 25 | testem.log 26 | /typings 27 | 28 | # e2e 29 | /e2e/*.js 30 | /e2e/*.map 31 | 32 | #System Files 33 | .DS_Store 34 | Thumbs.db 35 | -------------------------------------------------------------------------------- /src/app/router/app.routing.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | import { IndexComponent } from '../component/index/index.component'; 5 | import { TopicComponent } from '../component/topic/topic.component'; 6 | 7 | const appRoutes: Routes = [ 8 | { 9 | path: '', 10 | component: IndexComponent 11 | }, 12 | { 13 | path: 'topic/:id', 14 | component: TopicComponent 15 | } 16 | ]; 17 | 18 | export const routing: ModuleWithProviders = RouterModule.forRoot(appRoutes); -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CNode 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | Loading... 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/app/class/topic.ts: -------------------------------------------------------------------------------- 1 | class Author { 2 | loginname: string 3 | avatar_url: string 4 | } 5 | 6 | export class Reply { 7 | id: string 8 | author: Author 9 | content: string 10 | ups: string[] 11 | create_at: Date 12 | reply_id: string 13 | } 14 | 15 | export class Topic { 16 | id: string 17 | author_id: string 18 | tab: string 19 | content: string 20 | title: string 21 | last_reply_at: Date 22 | good: boolean 23 | top: boolean 24 | reply_count: number 25 | visit_count: number 26 | create_at: Date 27 | author: Author 28 | replies: Reply 29 | } 30 | -------------------------------------------------------------------------------- /src/app/service/store.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | import { Tab } from '../class/tab' 4 | 5 | @Injectable() 6 | export class StoreService { 7 | 8 | constructor() { } 9 | 10 | tabs: Tab[] = [ 11 | { 12 | text: '全部', 13 | val: '' 14 | }, 15 | { 16 | text: '精华', 17 | val: 'good' 18 | }, 19 | { 20 | text: '分享', 21 | val: 'share' 22 | }, 23 | { 24 | text: '问答', 25 | val: 'ask' 26 | }, 27 | { 28 | text: '招聘', 29 | val: 'job' 30 | } 31 | ] 32 | 33 | index: number = 0 34 | 35 | page: number = 1 36 | 37 | showToTop: boolean = false 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/markdown.css: -------------------------------------------------------------------------------- 1 | .markdown-text { 2 | padding: 10px; 3 | } 4 | .markdown-text img { 5 | max-width: 100%; 6 | } 7 | 8 | .markdown-text a { 9 | color: #80bd01; 10 | word-break: break-all; 11 | } 12 | 13 | .markdown-text h2, .markdown-text h3, .markdown-text h4, .markdown-text h5, .markdown-text h6 { 14 | position: relative; 15 | margin: 10px; 16 | } 17 | 18 | .markdown-text h2:before, .markdown-text h3:before, .markdown-text h4:before, .markdown-text h5:before, .markdown-text h6:before { 19 | content: "#"; 20 | color: #80bd01; 21 | position: absolute; 22 | left: -15px; 23 | font-weight: bold; 24 | } -------------------------------------------------------------------------------- /src/app/util/event-bus.ts: -------------------------------------------------------------------------------- 1 | export const EventBus = { 2 | events: [], 3 | 4 | on(name: string, fun: Function) { 5 | this.events.push({ 6 | name: name, 7 | fun: fun 8 | }); 9 | }, 10 | 11 | emit(name: string, param: any) { 12 | var event = this.events.find(x => x.name === name) 13 | if (event) { 14 | event.fun(param) 15 | } 16 | }, 17 | 18 | remove(name: string) { 19 | var event = this.events.find(x => x.name === name) 20 | if (event) { 21 | var index = this.events.indexOf(event) 22 | this.events.splice(index, 1) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | // This file includes polyfills needed by Angular 2 and is loaded before 2 | // the app. You can add your own extra polyfills to this file. 3 | import 'core-js/es6/symbol'; 4 | import 'core-js/es6/object'; 5 | import 'core-js/es6/function'; 6 | import 'core-js/es6/parse-int'; 7 | import 'core-js/es6/parse-float'; 8 | import 'core-js/es6/number'; 9 | import 'core-js/es6/math'; 10 | import 'core-js/es6/string'; 11 | import 'core-js/es6/date'; 12 | import 'core-js/es6/array'; 13 | import 'core-js/es6/regexp'; 14 | import 'core-js/es6/map'; 15 | import 'core-js/es6/set'; 16 | import 'core-js/es6/reflect'; 17 | 18 | import 'core-js/es7/reflect'; 19 | import 'zone.js/dist/zone'; 20 | -------------------------------------------------------------------------------- /src/app/component/head/head.component.css: -------------------------------------------------------------------------------- 1 | .tabs-ul { 2 | width: 100%; 3 | /*padding: 5px 0;*/ 4 | height: 48px; 5 | /*display: flex; 6 | flex-direction: row; 7 | flex-wrap: nowrap; 8 | justify-content: center;*/ 9 | } 10 | 11 | .tabs-ul li { 12 | float: left; 13 | width: 20%; 14 | /*max-width: 6.5rem;*/ 15 | /*padding: 3px 0;*/ 16 | text-align: center; 17 | line-height: 48px; 18 | font-weight: bold; 19 | } 20 | 21 | 22 | /*.tabs-ul .active { 23 | background-color: #80bd01; 24 | color: #fff; 25 | border-radius: 3px; 26 | }*/ 27 | 28 | .line { 29 | background-color: #80bd01; 30 | height: 2px; 31 | width: 20%; 32 | margin-left: 0; 33 | transition: all .5s; 34 | } -------------------------------------------------------------------------------- /src/app/component/head/head.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, HostListener } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { EventBus } from '../../util/event-bus' 5 | import { getScrollTop, getWindowHeight, getScrollHeight } from '../../util/dom' 6 | import { StoreService } from '../../service/store.service' 7 | 8 | @Component({ 9 | selector: 'app-head', 10 | templateUrl: './head.component.html', 11 | styleUrls: ['./head.component.css'] 12 | }) 13 | 14 | export class HeadComponent implements OnInit { 15 | 16 | check(i) { 17 | this.storeService.index = i 18 | EventBus.emit('indexChange', this.storeService.tabs[i].val) 19 | } 20 | 21 | constructor(private storeService: StoreService, private router: Router) { 22 | 23 | } 24 | 25 | ngOnInit() { 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/component/content-detail/content-detail.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { Topic } from '../../class/topic' 5 | 6 | @Component({ 7 | selector: 'app-content-detail', 8 | templateUrl: './content-detail.component.html', 9 | styleUrls: ['./content-detail.component.css'] 10 | }) 11 | 12 | 13 | export class ContentDetailComponent implements OnInit { 14 | 15 | tabs: Object = { 16 | share: '分享', 17 | ask: '问答', 18 | job: '招聘' 19 | } 20 | 21 | 22 | @Input() 23 | topic: Topic 24 | 25 | constructor(private router: Router) { } 26 | 27 | ngOnInit() { 28 | 29 | } 30 | 31 | getTab(tab: string) { 32 | return this.tabs[tab] 33 | } 34 | 35 | gotoDetail(): void { 36 | let link = ['/topic', this.topic.id]; 37 | this.router.navigate(link); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/docs/referenceConf.js 3 | 4 | /*global jasmine */ 5 | var SpecReporter = require('jasmine-spec-reporter'); 6 | 7 | exports.config = { 8 | allScriptsTimeout: 11000, 9 | specs: [ 10 | './e2e/**/*.e2e-spec.ts' 11 | ], 12 | capabilities: { 13 | 'browserName': 'chrome' 14 | }, 15 | directConnect: true, 16 | baseUrl: 'http://localhost:4200/', 17 | framework: 'jasmine', 18 | jasmineNodeOpts: { 19 | showColors: true, 20 | defaultTimeoutInterval: 30000, 21 | print: function() {} 22 | }, 23 | useAllAngular2AppRoots: true, 24 | beforeLaunch: function() { 25 | require('ts-node').register({ 26 | project: 'e2e' 27 | }); 28 | }, 29 | onPrepare: function() { 30 | jasmine.getEnv().addReporter(new SpecReporter()); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/app/pipe/interval.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'interval' 5 | }) 6 | export class IntervalPipe implements PipeTransform { 7 | 8 | transform(value: any): any { 9 | let date = new Date(value) 10 | if (!date) { 11 | return '' 12 | } 13 | let now = new Date() 14 | let year = now.getFullYear() - date.getFullYear() 15 | if (year) { 16 | return year + '年前' 17 | } 18 | let month = now.getMonth() - date.getMonth() 19 | if (month) { 20 | return month + '月前' 21 | } 22 | let day = now.getDate() - date.getDate() 23 | if (day) { 24 | return day + '天前' 25 | } 26 | let hour = now.getHours() - date.getHours() 27 | if (hour) { 28 | return hour + '小时前' 29 | } 30 | let min = now.getMinutes() - date.getMinutes() 31 | if (min) { 32 | return min + '分钟前' 33 | } 34 | 35 | return '刚刚'; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/app/component/reply/reply.component.css: -------------------------------------------------------------------------------- 1 | 2 | .avatar { 3 | height: 35px; 4 | width: 35px; 5 | border-radius: 100%; 6 | } 7 | 8 | .reply { 9 | background-color: #fff; 10 | margin-top: 10px; 11 | } 12 | 13 | .reply-avatar { 14 | width: 60px; 15 | text-align: center; 16 | float: right; 17 | } 18 | 19 | .reply-body { 20 | border-bottom: 1px solid #abc; 21 | margin-right: 60px; 22 | padding: 5px 0 10px 5px; 23 | } 24 | 25 | .reply-author { 26 | font-size: 12px; 27 | } 28 | 29 | .reply-title { 30 | margin-bottom: 4px; 31 | 32 | } 33 | 34 | .name { 35 | display: inline-block; 36 | width: 50px; 37 | font-size: 12px; 38 | overflow: hidden; 39 | white-space: nowrap; 40 | text-overflow: ellipsis; 41 | } 42 | 43 | .reply-tips { 44 | padding-left: 10px; 45 | } 46 | 47 | .reply-title>span { 48 | vertical-align: middle; 49 | } 50 | 51 | .reply-tips { 52 | height: 20px; 53 | line-height: 20px; 54 | } 55 | 56 | .reply-tips>* { 57 | vertical-align: middle; 58 | } -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | html { 4 | /*font-size: calc(100vw/3.75);*/ 5 | font-size: 62.5%; 6 | } 7 | 8 | body { 9 | margin: 0; 10 | padding: 0; 11 | font-size: 1.4rem; 12 | font-family: "Microsoft YaHei",sans-serif; 13 | } 14 | 15 | 16 | /* CSS Reset */ 17 | 18 | p, 19 | ul, 20 | li { 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | li { 26 | list-style: none; 27 | } 28 | 29 | 30 | /* CSS Reset End */ 31 | 32 | .text-green { 33 | color: #80bd01; 34 | } 35 | 36 | .bg-green { 37 | background-color: #80bd01; 38 | } 39 | 40 | .text-white { 41 | color: #fff; 42 | } 43 | 44 | .text-gray { 45 | color: #777; 46 | } 47 | 48 | .bg-gray { 49 | background-color: #444444; 50 | } 51 | 52 | .ul-across { 53 | height: auto; 54 | } 55 | 56 | .ul-across li { 57 | float: left; 58 | } 59 | 60 | .float-right { 61 | float: right; 62 | } 63 | 64 | .mg_l10 { 65 | margin-left: 10px; 66 | } 67 | 68 | .mg_t10 { 69 | margin-top: 10px; 70 | } -------------------------------------------------------------------------------- /src/app/component/content-detail/content-detail.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | {{topic.author.loginname}} 5 |
6 |
7 |
8 | {{topic.top?'置顶':topic.good ?'精华':''}} 9 | {{getTab(topic.tab)}} 10 | {{topic.title}} 11 |
12 |
13 | 14 | 15 | {{topic.reply_count}} 16 | /{{topic.visit_count}} 17 | 18 |
19 |
20 |
-------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', 'angular-cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-remap-istanbul'), 12 | require('angular-cli/plugins/karma') 13 | ], 14 | files: [ 15 | { pattern: './src/test.ts', watched: false } 16 | ], 17 | preprocessors: { 18 | './src/test.ts': ['angular-cli'] 19 | }, 20 | remapIstanbulReporter: { 21 | reports: { 22 | html: 'coverage', 23 | lcovonly: './coverage/coverage.lcov' 24 | } 25 | }, 26 | angularCli: { 27 | config: './angular-cli.json', 28 | environment: 'dev' 29 | }, 30 | reporters: ['progress', 'karma-remap-istanbul'], 31 | port: 9876, 32 | colors: true, 33 | logLevel: config.LOG_INFO, 34 | autoWatch: true, 35 | browsers: ['Chrome'], 36 | singleRun: false 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "version": "1.0.0-beta.17", 4 | "name": "cnode" 5 | }, 6 | "apps": [{ 7 | "root": "src", 8 | "outDir": "dist", 9 | "assets": "assets", 10 | "index": "index.html", 11 | "main": "main.ts", 12 | "test": "test.ts", 13 | "tsconfig": "tsconfig.json", 14 | "prefix": "app", 15 | "mobile": false, 16 | "styles": [ 17 | "styles.css", 18 | "markdown.css", 19 | "../node_modules/prismjs/themes/prism.css" 20 | ], 21 | "scripts": [], 22 | "environments": { 23 | "source": "environments/environment.ts", 24 | "dev": "environments/environment.ts", 25 | "prod": "environments/environment.prod.ts" 26 | } 27 | }], 28 | "addons": [], 29 | "packages": [], 30 | "e2e": { 31 | "protractor": { 32 | "config": "./protractor.conf.js" 33 | } 34 | }, 35 | "test": { 36 | "karma": { 37 | "config": "./karma.conf.js" 38 | } 39 | }, 40 | "defaults": { 41 | "styleExt": "css", 42 | "prefixInterfaces": false 43 | } 44 | } -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | 10 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 11 | declare var __karma__: any; 12 | declare var require: any; 13 | 14 | // Prevent Karma from running prematurely. 15 | __karma__.loaded = function () {}; 16 | 17 | 18 | Promise.all([ 19 | System.import('@angular/core/testing'), 20 | System.import('@angular/platform-browser-dynamic/testing') 21 | ]) 22 | // First, initialize the Angular testing environment. 23 | .then(([testing, testingBrowser]) => { 24 | testing.getTestBed().initTestEnvironment( 25 | testingBrowser.BrowserDynamicTestingModule, 26 | testingBrowser.platformBrowserDynamicTesting() 27 | ); 28 | }) 29 | // Then we find all the tests. 30 | .then(() => require.context('./', true, /\.spec\.ts/)) 31 | // And load the modules. 32 | .then(context => context.keys().map(context)) 33 | // Finally, start Karma to run the tests. 34 | .then(__karma__.start, __karma__.error); 35 | -------------------------------------------------------------------------------- /src/app/util/dom.ts: -------------------------------------------------------------------------------- 1 | //滚动条在Y轴上的滚动距离 2 | export function getScrollTop() { 3 | var scrollTop = 0, bodyScrollTop = 0, documentScrollTop = 0; 4 | if (document.body) { 5 | bodyScrollTop = document.body.scrollTop; 6 | } 7 | if (document.documentElement) { 8 | documentScrollTop = document.documentElement.scrollTop; 9 | } 10 | scrollTop = (bodyScrollTop - documentScrollTop > 0) ? bodyScrollTop : documentScrollTop; 11 | return scrollTop; 12 | } 13 | //文档的总高度 14 | export function getScrollHeight() { 15 | var scrollHeight = 0, bodyScrollHeight = 0, documentScrollHeight = 0; 16 | if (document.body) { 17 | bodyScrollHeight = document.body.scrollHeight; 18 | } 19 | if (document.documentElement) { 20 | documentScrollHeight = document.documentElement.scrollHeight; 21 | } 22 | scrollHeight = (bodyScrollHeight - documentScrollHeight > 0) ? bodyScrollHeight : documentScrollHeight; 23 | return scrollHeight; 24 | } 25 | //浏览器视口的高度 26 | export function getWindowHeight() { 27 | var windowHeight = 0; 28 | if (document.compatMode == "CSS1Compat") { 29 | windowHeight = document.documentElement.clientHeight; 30 | } else { 31 | windowHeight = document.body.clientHeight; 32 | } 33 | return windowHeight; 34 | }; -------------------------------------------------------------------------------- /src/app/service/topic.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Http, RequestOptions, RequestMethod } from '@angular/http'; 3 | import 'rxjs/add/operator/toPromise'; 4 | 5 | import { Topic } from '../class/topic' 6 | import { TopicParam } from '../class/param' 7 | import { Util } from '../util/util' 8 | 9 | const apiPrefix = 'https://cnodejs.org/api/v1' 10 | const api = { 11 | topics: apiPrefix + '/topics', 12 | topic: apiPrefix + '/topic' 13 | } 14 | 15 | @Injectable() 16 | export class TopicService { 17 | index: number = 0 18 | 19 | constructor(private http: Http) { } 20 | 21 | private handleError(error: any): Promise { 22 | console.error('An error occurred', error); // for demo purposes only 23 | return Promise.reject(error.message || error); 24 | } 25 | 26 | getTopics(param: TopicParam): Promise { 27 | return this.http.get(`${api.topics}?${Util.convertParamToString(param)}`) 28 | .toPromise() 29 | .then(response => response.json().data as Topic[]) 30 | .catch(this.handleError); 31 | } 32 | 33 | getTopicDetail(id: string): Promise { 34 | return this.http.get(`${api.topic}/${id}`) 35 | .toPromise() 36 | .then(response => response.json().data as Topic) 37 | .catch(this.handleError); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/app/component/topic/topic.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, HostListener, AfterViewInit } from '@angular/core'; 2 | import { Router, ActivatedRoute, Params } from '@angular/router'; 3 | import { TopicService } from '../../service/topic.service'; 4 | import { Topic } from '../../class/topic' 5 | var Prism = require('prismjs') 6 | 7 | @Component({ 8 | selector: 'app-topic', 9 | templateUrl: './topic.component.html', 10 | styleUrls: ['./topic.component.css'] 11 | }) 12 | export class TopicComponent implements OnInit, AfterViewInit { 13 | 14 | topic: Topic 15 | 16 | constructor( 17 | private router: Router, 18 | private route: ActivatedRoute, 19 | private topicService: TopicService 20 | ) { } 21 | 22 | ngOnInit() { 23 | this.route.params.forEach((params: Params) => { 24 | let id = params['id']; 25 | this.topicService.getTopicDetail(id).then(data => { 26 | this.topic = data 27 | }) 28 | }) 29 | } 30 | 31 | ngAfterViewInit() { 32 | setTimeout(() => { 33 | this.load() 34 | }, 100) 35 | } 36 | 37 | load() { 38 | let doms = document.getElementsByTagName('pre') 39 | for (let i = 0; i < doms.length; i++) { 40 | doms[i].className += 'language-javascript' 41 | doms[i].innerHTML = Prism.highlight(doms[i].innerText, Prism.languages.javascript) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/app/component/top/top.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, HostListener } from '@angular/core'; 2 | import { Router, ActivatedRoute, Params } from '@angular/router'; 3 | 4 | import { EventBus } from '../../util/event-bus' 5 | import { StoreService } from '../../service/store.service' 6 | import { getScrollTop, getWindowHeight, getScrollHeight } from '../../util/dom' 7 | 8 | @Component({ 9 | selector: 'app-top', 10 | templateUrl: './top.component.html', 11 | styleUrls: ['./top.component.css'] 12 | }) 13 | export class TopComponent implements OnInit { 14 | 15 | constructor(private storeService: StoreService, private route: ActivatedRoute) { } 16 | 17 | ngOnInit() { 18 | } 19 | 20 | @HostListener('window:scroll', ['$event']) 21 | scroll(event) { 22 | var scrollHeight = getScrollHeight() 23 | var ScrollTop = getScrollTop() 24 | var WindowHeight = getWindowHeight() 25 | 26 | if (location.pathname === '/' && scrollHeight - (ScrollTop + WindowHeight) <= 100 ) { 27 | EventBus.emit('nextPage', this.storeService.tabs[this.storeService.index].val) 28 | } 29 | if (ScrollTop > WindowHeight && !this.storeService.showToTop) { 30 | this.storeService.showToTop = true 31 | } else if (ScrollTop < WindowHeight && this.storeService.showToTop) { 32 | this.storeService.showToTop = false 33 | } 34 | } 35 | 36 | toTop() { 37 | document.body.scrollTop = 0 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cnode", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "angular-cli": {}, 6 | "scripts": { 7 | "start": "ng serve", 8 | "lint": "tslint \"src/**/*.ts\"", 9 | "test": "ng test", 10 | "pree2e": "webdriver-manager update", 11 | "e2e": "protractor", 12 | "server": "node build-server", 13 | "build": "ng build -prod" 14 | }, 15 | "private": true, 16 | "dependencies": { 17 | "@angular/common": "~2.0.0", 18 | "@angular/compiler": "~2.0.0", 19 | "@angular/core": "~2.0.0", 20 | "@angular/forms": "~2.0.0", 21 | "@angular/http": "~2.0.0", 22 | "@angular/material": "^2.0.0-alpha.9-3", 23 | "@angular/platform-browser": "~2.0.0", 24 | "@angular/platform-browser-dynamic": "~2.0.0", 25 | "@angular/router": "~3.0.0", 26 | "@types/hammerjs": "^2.0.33", 27 | "compression": "^1.6.2", 28 | "core-js": "^2.4.1", 29 | "express": "^4.14.0", 30 | "hammerjs": "^2.0.8", 31 | "prismjs": "^1.5.1", 32 | "rxjs": "5.0.0-beta.12", 33 | "ts-helpers": "^1.1.1", 34 | "zone.js": "^0.6.23" 35 | }, 36 | "devDependencies": { 37 | "@types/jasmine": "^2.2.30", 38 | "@types/node": "^6.0.42", 39 | "angular-cli": "1.0.0-beta.17", 40 | "codelyzer": "~0.0.26", 41 | "jasmine-core": "2.4.1", 42 | "jasmine-spec-reporter": "2.5.0", 43 | "karma": "1.2.0", 44 | "karma-chrome-launcher": "^2.0.0", 45 | "karma-cli": "^1.0.1", 46 | "karma-jasmine": "^1.0.2", 47 | "karma-remap-istanbul": "^0.2.1", 48 | "protractor": "4.0.9", 49 | "ts-node": "1.2.1", 50 | "tslint": "3.13.0", 51 | "typescript": "2.0.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { HttpModule } from '@angular/http'; 5 | import { MaterialModule } from '@angular/material'; 6 | import 'hammerjs' 7 | 8 | import { AppComponent } from './component/app.component'; 9 | import { HeadComponent } from './component/head/head.component'; 10 | import { ContentComponent } from './component/content/content.component'; 11 | 12 | import { IntervalPipe } from './pipe/interval.pipe' 13 | import { routing } from './router/app.routing'; 14 | import { TopicService } from './service/topic.service'; 15 | import { StoreService } from './service/store.service'; 16 | import { ContentDetailComponent } from './component/content-detail/content-detail.component'; 17 | import { IndexComponent } from './component/index/index.component'; 18 | import { TopicComponent } from './component/topic/topic.component'; 19 | import { TopComponent } from './component/top/top.component'; 20 | import { ReplyComponent } from './component/reply/reply.component'; 21 | 22 | @NgModule({ 23 | declarations: [ 24 | AppComponent, 25 | HeadComponent, 26 | ContentComponent, 27 | ContentDetailComponent, 28 | IntervalPipe, 29 | IndexComponent, 30 | TopicComponent, 31 | TopComponent, 32 | ReplyComponent 33 | ], 34 | imports: [ 35 | MaterialModule.forRoot(), 36 | BrowserModule, 37 | FormsModule, 38 | HttpModule, 39 | routing 40 | ], 41 | providers: [ 42 | TopicService, 43 | StoreService 44 | ], 45 | bootstrap: [AppComponent] 46 | }) 47 | export class AppModule { } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cnode 2 | 3 | > 这是本人学习angular2+Typescript的练手项目, 前端使用[angular2](https://angular.cn/)框架, 以及[material2](https://github.com/angular/material2)的部分组件,同时[Typescript](http://www.typescriptlang.org/)带的强类型和静态类型检查能够使开发更加稳定。 4 | 后端是使用cnode提供的官方[api](https://cnodejs.org/api)实现的 5 | 6 | ## Directory 7 | 8 | ``` 9 | . 10 | ├── e2e 测试 11 | ├── node_modules 12 | ├── src 13 | └─── app 14 | ├── class 类 15 | ├── component 组件 16 | ├── pipe 管道 17 | ├── router 路由 18 | ├── service 服务(依赖注入) 19 | ├── util 工具 20 | └── app.module.ts 应用入口 21 | ├── assets 资源 22 | ├── environments 环境配置 23 | ├── main.ts 入口 24 | └──tsconfig.json typescript配置 25 | ├── angular-cli 配置文件 26 | ├── tslint.json 27 | └── package.json 28 | 29 | ``` 30 | 31 | ## Api 32 | 33 | https://cnodejs.org/api 34 | 35 | 36 | ## angular-cli 37 | 38 | This project was generated with [angular-cli](https://github.com/angular/angular-cli) version 1.0.0-beta.17. 39 | 40 | ## Development server 41 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 42 | 43 | ## Code scaffolding 44 | 45 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class`. 46 | 47 | ## Build 48 | 49 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. 50 | 51 | ## Running unit tests 52 | 53 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 54 | 55 | ## Running end-to-end tests 56 | 57 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 58 | Before running the tests make sure you are serving the app via `ng serve`. 59 | 60 | ## Deploying to Github Pages 61 | 62 | Run `ng github-pages:deploy` to deploy to Github Pages. 63 | 64 | ## Further help 65 | 66 | To get more help on the `angular-cli` use `ng --help` or go check out the [Angular-CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 67 | -------------------------------------------------------------------------------- /src/app/component/content/content.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, OnDestroy } from '@angular/core'; 2 | 3 | import { TopicParam } from '../../class/param' 4 | import { Topic } from '../../class/topic' 5 | 6 | import { TopicService } from '../../service/topic.service' 7 | import { StoreService } from '../../service/store.service' 8 | import { EventBus } from '../../util/event-bus' 9 | 10 | @Component({ 11 | selector: 'app-content', 12 | templateUrl: './content.component.html', 13 | styleUrls: ['./content.component.css'] 14 | }) 15 | 16 | export class ContentComponent implements OnInit, OnDestroy { 17 | 18 | topics: Topic[] = [] 19 | isRequest: boolean = false 20 | 21 | constructor( 22 | private topicService: TopicService, 23 | private storeService: StoreService 24 | ) { 25 | 26 | } 27 | 28 | ngOnInit() { 29 | this.getTopics() 30 | 31 | EventBus.on('indexChange', (val) => { 32 | if (this.isRequest) { 33 | return 34 | } 35 | this.storeService.page = 1 36 | this.getTopics() 37 | }) 38 | 39 | EventBus.on('nextPage', (tab) => { 40 | if (this.isRequest) { 41 | return 42 | } 43 | this.storeService.page++ 44 | this.getTopics() 45 | }) 46 | } 47 | 48 | ngOnDestroy() { 49 | this.topics = [] 50 | this.storeService.page = 1 51 | EventBus.remove('indexChange') 52 | EventBus.remove('nextPage') 53 | } 54 | 55 | getTopics() { 56 | if (this.isRequest) { 57 | return 58 | } 59 | const {page, index, tabs} = this.storeService 60 | let tab = tabs[index].val 61 | this.isRequest = true 62 | let startTime:number = new Date().valueOf() 63 | this.topicService.getTopics(new TopicParam(page, tab)).then((data: Topic[]) => { 64 | if (page === 1) { 65 | setTimeout(() => { 66 | this.topics = data 67 | }, this.topics.length? 500 - (new Date().valueOf() - startTime)/1000 : 0) 68 | } else { 69 | this.topics = this.topics.concat(data) 70 | } 71 | 72 | this.isRequest = false 73 | }) 74 | } 75 | 76 | scroll(el) { 77 | console.log(el) 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/app/component/content-detail/content-detail.component.css: -------------------------------------------------------------------------------- 1 | /*.content-detail { 2 | height: 5rem; 3 | padding: 0.5rem 1rem 0.5rem 0; 4 | ; 5 | border-bottom: 0.1rem solid #80bd01; 6 | } 7 | 8 | .content-detail .avatar { 9 | height: 5rem; 10 | width: 5rem; 11 | margin-right: 1rem; 12 | float: left; 13 | border-radius: 100px; 14 | } 15 | 16 | .content-detail .content-detail-top { 17 | height: 3.5rem; 18 | } 19 | 20 | .content-detail .content-detail-top span.icon { 21 | vertical-align: top; 22 | padding: 0.2rem 0.4rem; 23 | border-radius: 0.3rem; 24 | font-size: 1.2rem; 25 | } 26 | 27 | .content-detail .content-detail-top span.top { 28 | background-color: #80bd01; 29 | color: #fff; 30 | } 31 | 32 | .content-detail .content-detail-top span.tab { 33 | background-color: #e5e5e5; 34 | color: #999; 35 | } 36 | 37 | .content-detail .content-detail-top .title { 38 | overflow: hidden; 39 | text-overflow: ellipsis; 40 | white-space: nowrap; 41 | width: calc(100% - 9.8rem); 42 | display: inline-block; 43 | } 44 | 45 | .content-detail .content-detail-info { 46 | font-size: 1.2rem; 47 | } 48 | 49 | .content-detail .content-detail-info time { 50 | margin-left: 1rem; 51 | }*/ 52 | 53 | span.top { 54 | background-color: #80bd01; 55 | color: #fff; 56 | } 57 | 58 | span.tab { 59 | background-color: #e5e5e5; 60 | color: #999; 61 | } 62 | 63 | span.icon { 64 | vertical-align: middle; 65 | padding: 0.2rem 0.4rem; 66 | border-radius: 0.3rem; 67 | font-size: 1.2rem; 68 | } 69 | 70 | .avatar { 71 | height: 35px; 72 | width: 35px; 73 | border-radius: 100%; 74 | } 75 | 76 | .name { 77 | display: inline-block; 78 | width: 50px; 79 | font-size: 12px; 80 | overflow: hidden; 81 | white-space: nowrap; 82 | text-overflow: ellipsis; 83 | } 84 | 85 | .content-detail { 86 | background-color: #fff; 87 | margin-top: 10px; 88 | } 89 | 90 | .content-detail-avatar { 91 | width: 60px; 92 | text-align: center; 93 | float: left; 94 | } 95 | 96 | .content-detail-body { 97 | border-bottom: 1px solid #abc; 98 | margin-left: 60px; 99 | padding: 5px 5px 5px 0; 100 | } 101 | 102 | .content-detail-author { 103 | font-size: 12px; 104 | } 105 | 106 | .content-detail-title { 107 | margin-bottom: 4px; 108 | } 109 | 110 | .content-detail-title>* { 111 | vertical-align: middle; 112 | } 113 | 114 | .content-detail-tips { 115 | height: 20px; 116 | line-height: 20px; 117 | } 118 | 119 | .content-detail-tips>* { 120 | vertical-align: middle; 121 | } 122 | 123 | 124 | /*.title { 125 | overflow: hidden; 126 | text-overflow: ellipsis; 127 | white-space: nowrap; 128 | width: calc(100% - 22rem); 129 | display: inline-block; 130 | }*/ 131 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "class-name": true, 7 | "comment-format": [ 8 | true, 9 | "check-space" 10 | ], 11 | "curly": true, 12 | "eofline": true, 13 | "forin": true, 14 | "indent": [ 15 | true, 16 | "spaces" 17 | ], 18 | "label-position": true, 19 | "label-undefined": true, 20 | "max-line-length": [ 21 | true, 22 | 140 23 | ], 24 | "member-access": false, 25 | "member-ordering": [ 26 | true, 27 | "static-before-instance", 28 | "variables-before-functions" 29 | ], 30 | "no-arg": true, 31 | "no-bitwise": true, 32 | "no-console": [ 33 | true, 34 | "debug", 35 | "info", 36 | "time", 37 | "timeEnd", 38 | "trace" 39 | ], 40 | "no-construct": true, 41 | "no-debugger": true, 42 | "no-duplicate-key": true, 43 | "no-duplicate-variable": true, 44 | "no-empty": false, 45 | "no-eval": true, 46 | "no-inferrable-types": true, 47 | "no-shadowed-variable": true, 48 | "no-string-literal": false, 49 | "no-switch-case-fall-through": true, 50 | "no-trailing-whitespace": true, 51 | "no-unused-expression": true, 52 | "no-unused-variable": true, 53 | "no-unreachable": true, 54 | "no-use-before-declare": true, 55 | "no-var-keyword": true, 56 | "object-literal-sort-keys": false, 57 | "one-line": [ 58 | true, 59 | "check-open-brace", 60 | "check-catch", 61 | "check-else", 62 | "check-whitespace" 63 | ], 64 | "quotemark": [ 65 | true, 66 | "single" 67 | ], 68 | "radix": true, 69 | "semicolon": [ 70 | "always" 71 | ], 72 | "triple-equals": [ 73 | true, 74 | "allow-null-check" 75 | ], 76 | "typedef-whitespace": [ 77 | true, 78 | { 79 | "call-signature": "nospace", 80 | "index-signature": "nospace", 81 | "parameter": "nospace", 82 | "property-declaration": "nospace", 83 | "variable-declaration": "nospace" 84 | } 85 | ], 86 | "variable-name": false, 87 | "whitespace": [ 88 | true, 89 | "check-branch", 90 | "check-decl", 91 | "check-operator", 92 | "check-separator", 93 | "check-type" 94 | ], 95 | 96 | "directive-selector-prefix": [true, "app"], 97 | "component-selector-prefix": [true, "app"], 98 | "directive-selector-name": [true, "camelCase"], 99 | "component-selector-name": [true, "kebab-case"], 100 | "directive-selector-type": [true, "attribute"], 101 | "component-selector-type": [true, "element"], 102 | "use-input-property-decorator": true, 103 | "use-output-property-decorator": true, 104 | "use-host-property-decorator": true, 105 | "no-input-rename": true, 106 | "no-output-rename": true, 107 | "use-life-cycle-interface": true, 108 | "use-pipe-transform-interface": true, 109 | "component-class-suffix": true, 110 | "directive-class-suffix": true 111 | } 112 | } 113 | --------------------------------------------------------------------------------