├── .babelrc
├── .editorconfig
├── .gitignore
├── README.md
├── angular-cli.json
├── browser
├── ipc
│ ├── api
│ │ ├── article.js
│ │ ├── index.js
│ │ └── session.js
│ └── index.js
├── screen
│ ├── console.js
│ ├── index.js
│ └── login.js
├── service
│ └── article.js
└── task
│ ├── base.js
│ ├── ifeng.js
│ └── index.js
├── e2e
├── app.e2e-spec.ts
├── app.po.ts
└── tsconfig.json
├── index.js
├── karma.conf.js
├── main.js
├── package.json
├── protractor.conf.js
├── src
├── app
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── app.routing.ts
│ ├── login
│ │ ├── login.component.html
│ │ ├── login.component.scss
│ │ ├── login.component.spec.ts
│ │ ├── login.component.ts
│ │ ├── login.service.spec.ts
│ │ └── login.service.ts
│ ├── main
│ │ ├── main-detail
│ │ │ ├── detail.service.spec.ts
│ │ │ ├── detail.service.ts
│ │ │ ├── main-detail.component.html
│ │ │ ├── main-detail.component.scss
│ │ │ ├── main-detail.component.spec.ts
│ │ │ └── main-detail.component.ts
│ │ ├── main-list
│ │ │ ├── list.service.spec.ts
│ │ │ ├── list.service.ts
│ │ │ ├── main-list.component.html
│ │ │ ├── main-list.component.scss
│ │ │ ├── main-list.component.spec.ts
│ │ │ └── main-list.component.ts
│ │ ├── main-menu
│ │ │ ├── main-menu.component.html
│ │ │ ├── main-menu.component.scss
│ │ │ ├── main-menu.component.spec.ts
│ │ │ ├── main-menu.component.ts
│ │ │ ├── menu.service.spec.ts
│ │ │ └── menu.service.ts
│ │ ├── main.component.html
│ │ ├── main.component.scss
│ │ ├── main.component.spec.ts
│ │ ├── main.component.ts
│ │ ├── main.module.ts
│ │ └── main.routing.ts
│ └── shared
│ │ ├── component
│ │ └── back
│ │ │ ├── back.component.html
│ │ │ ├── back.component.scss
│ │ │ ├── back.component.spec.ts
│ │ │ ├── back.component.ts
│ │ │ └── index.ts
│ │ ├── directive
│ │ └── back
│ │ │ ├── back.directive.ts
│ │ │ └── index.ts
│ │ ├── pipe
│ │ └── sanitize
│ │ │ ├── index.ts
│ │ │ └── sanitize.pipe.ts
│ │ ├── service
│ │ └── ipcRenderer
│ │ │ ├── index.ts
│ │ │ ├── ipc-renderer.service.spec.ts
│ │ │ └── ipc-renderer.service.ts
│ │ └── shared.module.ts
├── assets
│ ├── .gitkeep
│ ├── images
│ │ ├── default_photo.png
│ │ └── timg.jpeg
│ └── theme
│ │ ├── _normalize.scss
│ │ ├── _theme.scss
│ │ └── main.scss
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
├── styles.css
├── test.ts
└── tsconfig.json
└── tslint.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2016"
4 | ],
5 | "plugins": [
6 | "transform-async-to-generator",
7 | "syntax-async-functions",
8 | "transform-regenerator"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.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 |
10 | # IDEs and editors
11 | /.idea
12 | .project
13 | .classpath
14 | .c9/
15 | *.launch
16 | .settings/
17 |
18 | # IDE - VSCode
19 | .vscode/
20 | !.vscode/settings.json
21 | !.vscode/tasks.json
22 | !.vscode/launch.json
23 | !.vscode/extensions.json
24 |
25 | # misc
26 | /.sass-cache
27 | /connect.lock
28 | /coverage/*
29 | /libpeerconnection.log
30 | npm-debug.log
31 | testem.log
32 | /typings
33 |
34 | # e2e
35 | /e2e/*.js
36 | /e2e/*.map
37 |
38 | #System Files
39 | .DS_Store
40 | Thumbs.db
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # news-feed
2 | 跟随教程搭建基于 Angular 的 Electron 的爬虫应用
3 |
4 | ### 简介
5 | Angular 推出有一段时间,很多人都开始尝试使用它构建一些有意思的应用,但目前国内教程非常稀少,很少有人针对代码风格、
6 | 架构、框架之间的结合、编程细节来分析。这里我结合了 Electron 来演示如何构建一个桌面应用。
7 |
8 | 我希望能够丰富大家的编程经验,教程中添加了一些额外的常见场景,希望你能够跟随教程快速掌握 Angular 与 Electron。
9 |
10 | ### 教程可参见博客系列文章:
11 |
12 | [使用 Angular 构建 Electron 应用](https://github.com/WittBulter/blog/tree/master/posts/electron)
13 |
14 | ### 特点
15 | * 需要一定的 javascript 编程基础
16 | * 着重于思想而非照抄代码学习编程
17 | * 涵盖众多业务范畴
18 | * 技术栈新颖,实用性很强
19 | * 构建桌面应用,富有趣味性
20 |
--------------------------------------------------------------------------------
/angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "project": {
3 | "version": "1.0.0-beta.25.5",
4 | "name": "renderer"
5 | },
6 | "apps": [
7 | {
8 | "root": "src",
9 | "outDir": "dist",
10 | "assets": [
11 | "assets",
12 | "favicon.ico"
13 | ],
14 | "index": "index.html",
15 | "main": "main.ts",
16 | "test": "test.ts",
17 | "tsconfig": "tsconfig.json",
18 | "prefix": "app",
19 | "mobile": false,
20 | "styles": [
21 | "styles.css",
22 | "assets/theme/main.scss"
23 | ],
24 | "scripts": [],
25 | "environments": {
26 | "source": "environments/environment.ts",
27 | "dev": "environments/environment.ts",
28 | "prod": "environments/environment.prod.ts"
29 | }
30 | }
31 | ],
32 | "addons": [],
33 | "packages": [],
34 | "e2e": {
35 | "protractor": {
36 | "config": "./protractor.conf.js"
37 | }
38 | },
39 | "test": {
40 | "karma": {
41 | "config": "./karma.conf.js"
42 | }
43 | },
44 | "defaults": {
45 | "styleExt": "scss",
46 | "prefixInterfaces": false,
47 | "inline": {
48 | "style": false,
49 | "template": false
50 | },
51 | "spec": {
52 | "class": false,
53 | "component": true,
54 | "directive": true,
55 | "module": false,
56 | "pipe": true,
57 | "service": true
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/browser/ipc/api/article.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/3/2.
3 | */
4 | const articleService = require('../../service/article')
5 |
6 | module.exports = {
7 | list: async(e, page) => {
8 | try {
9 | const articles = await articleService.findArticlesForPage(page)
10 | // todo filter articles
11 | return articles
12 | } catch (err) {
13 | return Promise.reject(err)
14 | }
15 | },
16 | detail: async(e, id) => {
17 | try {
18 | const article = await articleService.findArticleForID(id)
19 | return article
20 | } catch (err) {
21 | return Promise.reject(err)
22 | }
23 | },
24 | }
25 |
--------------------------------------------------------------------------------
/browser/ipc/api/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/12.
3 | */
4 |
5 | module.exports = Object.assign(
6 | {},
7 | require('./article'),
8 | require('./session'),
9 | )
10 |
--------------------------------------------------------------------------------
/browser/ipc/api/session.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/3/2.
3 | */
4 |
5 | const screen = require('../../screen')
6 |
7 | module.exports = {
8 | login: async(e, user) => {
9 | // todo something
10 | screen.setSize(1000, 720)
11 | return { msg: 'ok' }
12 | },
13 | logout: async() => {
14 | // todo something
15 | screen.setSize(700, 500)
16 | return { msg: 'ok' }
17 | },
18 | }
19 |
--------------------------------------------------------------------------------
/browser/ipc/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/12.
3 | */
4 |
5 | const { ipcMain } = require('electron')
6 | const api = require('./api')
7 |
8 | ipcMain.on('api', (event, actionName, ...args) => {
9 | const reply = (replayObj, status = 'success') => {
10 | event.sender.send(`${actionName}reply`, replayObj, status)
11 | }
12 | if (api[actionName]) {
13 | api[actionName](event, ...args)
14 | .then(res => reply(res))
15 | .catch(err => reply({ message: '应用出现了错误' }))
16 | }
17 | })
18 |
--------------------------------------------------------------------------------
/browser/screen/console.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/16.
3 | */
4 | const { app, BrowserWindow } = require('electron')
5 |
6 | module.exports = new class self {
7 | constructor() {
8 | }
9 |
10 | open(url) {
11 | const win = new BrowserWindow({
12 | width: 1000,
13 | height: 720,
14 | show: false,
15 | frame: false,
16 | resizable: true,
17 | })
18 | win.loadURL(`${url}`)
19 | win.webContents.openDevTools()
20 | return win
21 | }
22 | }()
23 |
24 |
--------------------------------------------------------------------------------
/browser/screen/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/16.
3 | */
4 | const login = require('./login')
5 | const console = require('./console')
6 | const windowList = {
7 | login: login,
8 | console: console,
9 | }
10 |
11 | module.exports = new class Screen {
12 | constructor() {
13 | this.win = null
14 | this.baseUrl = ''
15 | }
16 |
17 | static show(win) {
18 | win.show()
19 | win.focus()
20 | }
21 |
22 | // 打开一个窗口 默认打开登录窗口
23 | open(winName = 'login') {
24 | if (!windowList[winName]) return
25 | this.win = windowList[winName].open(this.baseUrl)
26 |
27 | this.win.on('closed', () => this.win = null)
28 | this.win.on('ready-to-show', () => Screen.show(this.win))
29 | }
30 |
31 | setBaseUrl(baseUrl) {
32 | this.baseUrl = baseUrl
33 | return this
34 | }
35 |
36 | activate() {
37 | this.win === null && this.open()
38 | }
39 |
40 | setSize(w, h) {
41 | if (this.win) {
42 | const bounds = this.win.getBounds()
43 | const newBounds = {
44 | x: bounds.x - (w - bounds.width) / 2,
45 | y: bounds.y - (h - bounds.height) / 2,
46 | }
47 | this.win.setBounds({
48 | x: newBounds.x,
49 | y: newBounds.y,
50 | width: w,
51 | height: h,
52 | }, true)
53 | }
54 | }
55 | }()
56 |
--------------------------------------------------------------------------------
/browser/screen/login.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/16.
3 | */
4 | const { app, BrowserWindow } = require('electron')
5 |
6 | module.exports = new class Login {
7 | constructor() {
8 | }
9 |
10 | open(url) {
11 | const win = new BrowserWindow({
12 | width: 700,
13 | height: 500,
14 | show: false,
15 | frame: false,
16 | resizable: true,
17 | })
18 | win.loadURL(url)
19 | win.webContents.openDevTools()
20 | return win
21 | }
22 | }()
23 |
24 |
--------------------------------------------------------------------------------
/browser/service/article.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/21.
3 | */
4 |
5 | module.exports = {
6 | findArticlesForPage: page => {
7 | return new Promise((resolve, reject) => {
8 | global.Storage
9 | .find({})
10 | .projection({ content: 0 })
11 | .sort({ id: 1 })
12 | .skip(page)
13 | .limit(15)
14 | .exec((err, data) => {
15 | if (err) return reject(err)
16 | resolve(data)
17 | })
18 | })
19 | },
20 | findArticleForID: id => {
21 | return new Promise((resolve, reject) => {
22 | global.Storage
23 | .findOne({ _id: id })
24 | .exec((err, data) => {
25 | if (err) return reject(err)
26 | resolve(data)
27 | })
28 | })
29 | },
30 | }
31 |
--------------------------------------------------------------------------------
/browser/task/base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/20.
3 | */
4 | const req = require('request')
5 |
6 | module.exports = class Base {
7 | constructor() {
8 |
9 | }
10 |
11 | static makeOptions(url) {
12 | return {
13 | url: url,
14 | port: 8080,
15 | method: 'GET',
16 | headers: {
17 | 'User-Agent': 'nodejs',
18 | 'Content-Type': 'application/json',
19 | },
20 | }
21 | }
22 |
23 | static request(url) {
24 | return new Promise((resolve, reject) => {
25 | req(Base.makeOptions(url), (err, response, body) => {
26 | if (err) return reject(err)
27 | resolve(body)
28 | })
29 | })
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/browser/task/ifeng.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/20.
3 | */
4 | const cheerio = require('cheerio')
5 | const Base = require('./base')
6 |
7 | module.exports = new class Self extends Base {
8 | constructor() {
9 | super()
10 | this.url = 'http://news.ifeng.com/xijinping/'
11 | }
12 |
13 | start() {
14 | global.Storage.count({}, (err, c) => {
15 | if (c || c > 0) return
16 | this.request()
17 | .then(res => {
18 | console.log('全部储存完毕!')
19 | global.Storage.loadDatabase()
20 | })
21 | .catch(err => {
22 | console.log(err)
23 | })
24 | })
25 |
26 | }
27 |
28 | async request() {
29 | try {
30 | const body = await Self.request(this.url)
31 | let links = await this.parseLink(body)
32 | for (let index = 1; index < links.length; index++) {
33 | const content = await Self.request(links[index - 1])
34 | const article = await this.parseContent(content)
35 | await this.saveContent(Object.assign({ id: index }, article))
36 | console.log(`第${index}篇文章:[${article && article.title}] 储存完毕`)
37 | }
38 | } catch (err) {
39 | return Promise.reject(err)
40 | }
41 | }
42 |
43 | parseLink(html) {
44 | const $ = cheerio.load(html)
45 | return $('.con_lis > a')
46 | .map((i, el) => $(el)
47 | .attr('href'))
48 | }
49 |
50 | parseContent(html) {
51 | if (!html) return
52 | const $ = cheerio.load(html)
53 | const title = $('title')
54 | .text()
55 | const description = $('meta[name="description"]')
56 | .attr('content')
57 | const content = $('.yc_con_txt')
58 | .html()
59 | const hot = $('span.js_joinNum')
60 | .text()
61 | return {
62 | title: title,
63 | content: content,
64 | description: description,
65 | hot: hot,
66 | createdAt: new Date(),
67 | }
68 | }
69 |
70 | saveContent(article) {
71 | if (!article || !article.title) return
72 | return global.Storage.insert(article)
73 | }
74 | }()
75 |
76 |
--------------------------------------------------------------------------------
/browser/task/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/20.
3 | */
4 |
5 | module.exports = {
6 | ifeng: require('./ifeng'),
7 | }
8 |
--------------------------------------------------------------------------------
/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { RendererPage } from './app.po';
2 |
3 | describe('renderer App', function() {
4 | let page: RendererPage;
5 |
6 | beforeEach(() => {
7 | page = new RendererPage();
8 | });
9 |
10 | it('should display message saying app works', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('app works!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, element, by } from 'protractor';
2 |
3 | export class RendererPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('app-root h1')).getText();
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 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/11.
3 | */
4 | const { app } = require('electron')
5 | const screen = require('./browser/screen')
6 | require('./browser/ipc/index')
7 | const Datastore = require('nedb')
8 | const url = `file://${__dirname}/dist/index.html`
9 |
10 | app.on('ready', _ => screen.setBaseUrl(url).open())
11 | app.on('window-all-closed', _ => process.platform !== 'darwin' && app.quit())
12 |
13 | app.on('activate', _ => screen.activate())
14 | global.Storage = new Datastore({ filename: `${__dirname}/.database/news-feed.db`, autoload: true })
15 |
16 | require('./browser/task/index').ifeng.start()
17 |
--------------------------------------------------------------------------------
/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 | mime: {
21 | 'text/x-typescript': ['ts','tsx']
22 | },
23 | remapIstanbulReporter: {
24 | reports: {
25 | html: 'coverage',
26 | lcovonly: './coverage/coverage.lcov'
27 | }
28 | },
29 | angularCli: {
30 | config: './angular-cli.json',
31 | environment: 'dev'
32 | },
33 | reporters: config.angularCli && config.angularCli.codeCoverage
34 | ? ['progress', 'karma-remap-istanbul']
35 | : ['progress'],
36 | port: 9876,
37 | colors: true,
38 | logLevel: config.LOG_INFO,
39 | autoWatch: true,
40 | browsers: ['Chrome'],
41 | singleRun: false
42 | });
43 | };
44 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/20.
3 | */
4 | 'use strict'
5 |
6 | require('babel-core/register')
7 | require('babel-polyfill')
8 | require('./index')
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "news-feed",
3 | "version": "0.0.1",
4 | "description": "基于Angular的Electron应用",
5 | "main": "index.js",
6 | "scripts": {
7 | "ng": "ng",
8 | "start": "ng serve",
9 | "lint": "tslint \"src/**/*.ts\" --project src/tsconfig.json --type-check && tslint \"e2e/**/*.ts\" --project e2e/tsconfig.json --type-check",
10 | "test": "ng test",
11 | "pree2e": "webdriver-manager update --standalone false --gecko false",
12 | "e2e": "protractor",
13 | "watch": "ng build -watch -o dist/",
14 | "ele": "electron main.js"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://WittBulter@github.com/WittBulter/news-feed.git"
19 | },
20 | "author": "WittBulter (nanazuimeng123@gmail.com)",
21 | "license": "ISC",
22 | "bugs": {
23 | "url": "https://github.com/WittBulter/news-feed/issues"
24 | },
25 | "homepage": "https://github.com/WittBulter/news-feed#readme",
26 | "dependencies": {
27 | "@angular/common": "^2.3.1",
28 | "@angular/compiler": "^2.3.1",
29 | "@angular/core": "^2.3.1",
30 | "@angular/forms": "^2.3.1",
31 | "@angular/http": "^2.3.1",
32 | "@angular/platform-browser": "^2.3.1",
33 | "@angular/platform-browser-dynamic": "^2.3.1",
34 | "@angular/router": "^3.3.1",
35 | "babel-core": "^6.23.1",
36 | "babel-plugin-syntax-async-functions": "^6.13.0",
37 | "babel-plugin-transform-async-to-generator": "^6.22.0",
38 | "babel-plugin-transform-regenerator": "^6.22.0",
39 | "babel-polyfill": "^6.23.0",
40 | "babel-preset-es2016": "^6.22.0",
41 | "cheerio": "^0.22.0",
42 | "core-js": "^2.4.1",
43 | "electron": "^1.4.15",
44 | "nedb": "^1.8.0",
45 | "request": "^2.79.0",
46 | "rxjs": "^5.0.1",
47 | "ts-helpers": "^1.1.1",
48 | "zone.js": "^0.7.2"
49 | },
50 | "devDependencies": {
51 | "@angular/compiler-cli": "^2.3.1",
52 | "@types/jasmine": "2.5.38",
53 | "@types/node": "^6.0.42",
54 | "angular-cli": "1.0.0-beta.25.5",
55 | "codelyzer": "~2.0.0-beta.1",
56 | "jasmine-core": "2.5.2",
57 | "jasmine-spec-reporter": "2.5.0",
58 | "karma": "1.2.0",
59 | "karma-chrome-launcher": "^2.0.0",
60 | "karma-cli": "^1.0.1",
61 | "karma-jasmine": "^1.0.2",
62 | "karma-remap-istanbul": "^0.2.1",
63 | "protractor": "~4.0.13",
64 | "ts-node": "1.2.1",
65 | "tslint": "^4.3.0",
66 | "typescript": "~2.0.3"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
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 | },
24 | useAllAngular2AppRoots: true,
25 | beforeLaunch: function () {
26 | require('ts-node')
27 | .register({
28 | project: 'e2e',
29 | })
30 | },
31 | onPrepare: function () {
32 | jasmine.getEnv()
33 | .addReporter(new SpecReporter())
34 | },
35 | }
36 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/app/app.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unix/news-feed/e9156630a2dbbddd67e73fa69383962253ad579b/src/app/app.component.scss
--------------------------------------------------------------------------------
/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 |
3 | import { async, TestBed } from '@angular/core/testing'
4 | import { AppComponent } from './app.component'
5 |
6 | describe('AppComponent', () => {
7 | beforeEach(() => {
8 | TestBed.configureTestingModule({
9 | declarations: [
10 | AppComponent,
11 | ],
12 | })
13 | TestBed.compileComponents()
14 | })
15 |
16 | it('should create the app', async(() => {
17 | const fixture = TestBed.createComponent(AppComponent)
18 | const app = fixture.debugElement.componentInstance
19 | expect(app)
20 | .toBeTruthy()
21 | }))
22 |
23 | it(`should have as title 'app works!'`, async(() => {
24 | const fixture = TestBed.createComponent(AppComponent)
25 | const app = fixture.debugElement.componentInstance
26 | expect(app.title)
27 | .toEqual('app works!')
28 | }))
29 |
30 | it('should render title in a h1 tag', async(() => {
31 | const fixture = TestBed.createComponent(AppComponent)
32 | fixture.detectChanges()
33 | const compiled = fixture.debugElement.nativeElement
34 | expect(compiled.querySelector('h1').textContent)
35 | .toContain('app works!')
36 | }))
37 | })
38 |
--------------------------------------------------------------------------------
/src/app/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.scss'],
7 | providers: [],
8 | })
9 | export class AppComponent {
10 | title = 'app works!'
11 | }
12 |
--------------------------------------------------------------------------------
/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 { RouterModule } from '@angular/router'
6 | import { AppRoutingModule } from './app.routing'
7 |
8 | import { SharedModule } from './shared/shared.module'
9 |
10 | import { AppComponent } from './app.component'
11 | import { LoginComponent } from './login/login.component'
12 |
13 | @NgModule({
14 | declarations: [
15 | AppComponent,
16 | LoginComponent,
17 | ],
18 | imports: [
19 | BrowserModule,
20 | FormsModule,
21 | HttpModule,
22 | SharedModule.forRoot(),
23 | RouterModule,
24 | AppRoutingModule,
25 | ],
26 | providers: [],
27 | bootstrap: [AppComponent],
28 | })
29 | export class AppModule {
30 | }
31 |
--------------------------------------------------------------------------------
/src/app/app.routing.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/12.
3 | */
4 |
5 |
6 | import { NgModule } from '@angular/core'
7 | import { RouterModule, Routes } from '@angular/router'
8 |
9 | import { LoginComponent } from './login/login.component'
10 |
11 | export const appRoutes: Routes = [
12 | { path: '', redirectTo: 'login', pathMatch: 'full' },
13 | { path: 'login', component: LoginComponent },
14 | { path: 'login/*', component: LoginComponent },
15 | { path: 'main', loadChildren: './main/main.module#MainModule', data: { preload: true } },
16 | ]
17 |
18 | @NgModule({
19 | imports: [RouterModule.forRoot(appRoutes)],
20 | exports: [RouterModule],
21 | })
22 | export class AppRoutingModule {
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/login/login.component.html:
--------------------------------------------------------------------------------
1 |
17 |
--------------------------------------------------------------------------------
/src/app/login/login.component.scss:
--------------------------------------------------------------------------------
1 | .login {
2 | width: 100%;
3 | height: 100%;
4 | .top {
5 | width: 100%;
6 | height: 55%;
7 | background: url("assets/images/timg.jpeg") no-repeat;
8 | background-size: cover;
9 | background-position: center center;
10 | }
11 | .bottom {
12 | width: 100%;
13 | padding: 20px 0;
14 | .login-box {
15 | width: 50%;
16 | max-width: 230px;
17 | margin: 0 auto;
18 | border: 1px solid #ddd;
19 | border-radius: 2px;
20 | overflow: hidden;
21 | .input-box {
22 | width: 100%;
23 | &:last-child {
24 | border-top: 1px solid #ddd;
25 | }
26 | > input {
27 | width: 100%;
28 | height: 35px;
29 | padding-left: 3px;
30 | font-size: 18px;
31 | border: none;
32 | &:active, &:focus {
33 | outline: none;
34 | }
35 | }
36 | }
37 | }
38 | button {
39 | width: 50%;
40 | max-width: 230px;
41 | margin: 15px auto;
42 | height: 40px;
43 | border-radius: 3px;
44 | background: #08D088;
45 | color: #EEFAF6;
46 | font-size: 18px;
47 | display: block;
48 | border: none;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/app/login/login.component.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'
3 |
4 | import { LoginComponent } from './login.component'
5 |
6 | describe('LoginComponent', () => {
7 | let component: LoginComponent
8 | let fixture: ComponentFixture
9 |
10 | beforeEach(async(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [LoginComponent],
13 | })
14 | .compileComponents()
15 | }))
16 |
17 | beforeEach(() => {
18 | fixture = TestBed.createComponent(LoginComponent)
19 | component = fixture.componentInstance
20 | fixture.detectChanges()
21 | })
22 |
23 | it('should create', () => {
24 | expect(component)
25 | .toBeTruthy()
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/src/app/login/login.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core'
2 | import { LoginService } from './login.service'
3 | import { Router } from '@angular/router'
4 |
5 | @Component({
6 | selector: 'app-login',
7 | templateUrl: './login.component.html',
8 | styleUrls: ['./login.component.scss'],
9 | providers: [LoginService],
10 | })
11 | export class LoginComponent {
12 |
13 | constructor(
14 | private loginService: LoginService,
15 | private router: Router,
16 | ) {
17 | }
18 |
19 |
20 | login(name: string, pw: string) {
21 | this.loginService.login({
22 | name: name,
23 | pw: pw,
24 | })
25 | .subscribe(next => this.router.navigate(['/main']))
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/app/login/login.service.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 |
3 | import { inject, TestBed } from '@angular/core/testing'
4 | import { LoginService } from './login.service'
5 |
6 | describe('LoginService', () => {
7 | beforeEach(() => {
8 | TestBed.configureTestingModule({
9 | providers: [LoginService],
10 | })
11 | })
12 |
13 | it('should ...', inject([LoginService], (service: LoginService) => {
14 | expect(service)
15 | .toBeTruthy()
16 | }))
17 | })
18 |
--------------------------------------------------------------------------------
/src/app/login/login.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core'
2 | import { IpcRendererService } from '../shared/service/ipcRenderer'
3 | import { Observable } from 'rxjs/Observable'
4 | import 'rxjs/Rx'
5 |
6 | @Injectable()
7 | export class LoginService {
8 |
9 | constructor(private ipcRendererService: IpcRendererService) {
10 | }
11 |
12 | login(user: any): Observable {
13 | return Observable.fromPromise(this.ipcRendererService.api('login', user))
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/main/main-detail/detail.service.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 |
3 | import { inject, TestBed } from '@angular/core/testing'
4 | import { DetailService } from './detail.service'
5 |
6 | describe('DetailService', () => {
7 | beforeEach(() => {
8 | TestBed.configureTestingModule({
9 | providers: [DetailService],
10 | })
11 | })
12 |
13 | it('should ...', inject([DetailService], (service: DetailService) => {
14 | expect(service)
15 | .toBeTruthy()
16 | }))
17 | })
18 |
--------------------------------------------------------------------------------
/src/app/main/main-detail/detail.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core'
2 | import { IpcRendererService } from '../../shared/service/ipcRenderer'
3 | import { Observable } from 'rxjs/Observable'
4 | import 'rxjs/Rx'
5 |
6 | @Injectable()
7 | export class DetailService {
8 |
9 | constructor(private ipcRendererService: IpcRendererService) {
10 | }
11 |
12 | getDetail(id: string): Observable {
13 | return Observable.fromPromise(this.ipcRendererService.api('detail', id))
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/main/main-detail/main-detail.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | 返回列表
4 |
5 |
6 |
{{article.title}}
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/app/main/main-detail/main-detail.component.scss:
--------------------------------------------------------------------------------
1 | .detail {
2 | padding: 50px 15px;
3 | width: 100%;
4 | height: auto;
5 | min-height: 100%;
6 | > h2 {
7 | font-size: 30px;
8 | line-height: 34px;
9 | font-weight: 400;
10 | }
11 |
12 | }
13 |
14 | .back {
15 | position: fixed;
16 | top: 10px;
17 | width: auto;
18 | height: auto;
19 | -webkit-user-select: none;
20 | -moz-user-select: none;
21 | -ms-user-select: none;
22 | user-select: none;
23 | cursor: pointer;
24 | background: rgba(121, 130, 152, 0.88);
25 | transition: all .15s;
26 | box-shadow: 0 5px 10px rgba(0, 0, 0, .1);
27 | padding: 8px 18px;
28 | border-radius: 3px;
29 | color: #fff;
30 | &:hover {
31 | background: rgba(121, 130, 152, 1);
32 | box-shadow: 0 5px 10px rgba(0, 0, 0, .25);
33 | }
34 | > span {
35 | font-size: 16px;
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/app/main/main-detail/main-detail.component.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'
3 |
4 | import { MainDetailComponent } from './main-detail.component'
5 |
6 | describe('MainDetailComponent', () => {
7 | let component: MainDetailComponent
8 | let fixture: ComponentFixture
9 |
10 | beforeEach(async(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [MainDetailComponent],
13 | })
14 | .compileComponents()
15 | }))
16 |
17 | beforeEach(() => {
18 | fixture = TestBed.createComponent(MainDetailComponent)
19 | component = fixture.componentInstance
20 | fixture.detectChanges()
21 | })
22 |
23 | it('should create', () => {
24 | expect(component)
25 | .toBeTruthy()
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/src/app/main/main-detail/main-detail.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core'
2 | import { ActivatedRoute, Params, Router } from '@angular/router'
3 | import { DetailService } from './detail.service'
4 |
5 | @Component({
6 | selector: 'app-main-detail',
7 | templateUrl: './main-detail.component.html',
8 | styleUrls: ['./main-detail.component.scss'],
9 | providers: [DetailService],
10 | })
11 | export class MainDetailComponent implements OnInit {
12 |
13 | constructor(private route: ActivatedRoute,
14 | private router: Router,
15 | private detailService: DetailService) {
16 | }
17 |
18 | public article: any
19 |
20 | getDetail(id: string) {
21 | this.detailService.getDetail(id)
22 | .subscribe(
23 | res => {
24 | this.article = res
25 | },
26 | )
27 | }
28 |
29 | ngOnInit() {
30 | this.route.params.forEach((params: Params) => {
31 | const id = params['id']
32 | if (id) this.getDetail(id)
33 | })
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/src/app/main/main-list/list.service.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 |
3 | import { inject, TestBed } from '@angular/core/testing'
4 | import { ListService } from './list.service'
5 |
6 | describe('ListService', () => {
7 | beforeEach(() => {
8 | TestBed.configureTestingModule({
9 | providers: [ListService],
10 | })
11 | })
12 |
13 | it('should ...', inject([ListService], (service: ListService) => {
14 | expect(service)
15 | .toBeTruthy()
16 | }))
17 | })
18 |
--------------------------------------------------------------------------------
/src/app/main/main-list/list.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core'
2 | import { IpcRendererService } from '../../shared/service/ipcRenderer'
3 | import { Observable } from 'rxjs/Observable'
4 | import 'rxjs/Rx'
5 |
6 | @Injectable()
7 | export class ListService {
8 |
9 | constructor(private ipcRendererService: IpcRendererService) {
10 | }
11 |
12 | getList(page: any): Observable {
13 | return Observable.fromPromise(this.ipcRendererService.api('list', page))
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/main/main-list/main-list.component.html:
--------------------------------------------------------------------------------
1 |
9 |
10 | 加载更多
11 |
12 |
--------------------------------------------------------------------------------
/src/app/main/main-list/main-list.component.scss:
--------------------------------------------------------------------------------
1 | .list {
2 | width: 100%;
3 | height: auto;
4 | min-height: 100%;
5 | background: #fff;
6 | margin: 0 auto;
7 | padding: 10px 0;
8 | > li {
9 | width: 100%;
10 | padding: 13px 25px;
11 | height: auto;
12 | border-bottom: 1px solid #D9DFEB;
13 | background: #fff;
14 | transition: all, 0.2s;
15 | cursor: pointer;
16 | > h2 {
17 | font-size: 22px;
18 | line-height: 25px;
19 | font-weight: normal;
20 | -webkit-user-select: none;
21 | -moz-user-select: none;
22 | -ms-user-select: none;
23 | user-select: none;
24 | }
25 | .text {
26 | font-size: 16px;
27 | font-weight: 300;
28 | margin-top: 5px;
29 | font-family: Helvetica Neue, Helvetica, Arial, sans-serif !important;
30 | }
31 | &:last-child {
32 | border-bottom: none;
33 | }
34 | &:hover {
35 | background: #d7dbe6;
36 | }
37 | }
38 | }
39 |
40 | .more {
41 | width: 100%;
42 | height: auto;
43 | padding: 25px 0;
44 | margin-bottom: 30px;
45 | text-align: center;
46 | -webkit-user-select: none;
47 | -moz-user-select: none;
48 | -ms-user-select: none;
49 | user-select: none;
50 | cursor: pointer;
51 | > span {
52 | font-size: 16px;
53 | background: transparent;
54 | transition: all .15s;
55 | padding: 8px 18px;
56 | border-radius: 3px;
57 | border: 1px solid #d5d5d5;
58 | color: #9e9e9e;
59 | &:hover {
60 | color: #444;
61 | border-color: #444;
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/app/main/main-list/main-list.component.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'
3 |
4 | import { MainListComponent } from './main-list.component'
5 |
6 | describe('MainListComponent', () => {
7 | let component: MainListComponent
8 | let fixture: ComponentFixture
9 |
10 | beforeEach(async(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [MainListComponent],
13 | })
14 | .compileComponents()
15 | }))
16 |
17 | beforeEach(() => {
18 | fixture = TestBed.createComponent(MainListComponent)
19 | component = fixture.componentInstance
20 | fixture.detectChanges()
21 | })
22 |
23 | it('should create', () => {
24 | expect(component)
25 | .toBeTruthy()
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/src/app/main/main-list/main-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnDestroy, OnInit } from '@angular/core'
2 | import { Router } from '@angular/router'
3 | import { BehaviorSubject, Observable, Subscription } from 'rxjs'
4 | import { ListService } from './list.service'
5 |
6 | @Component({
7 | selector: 'app-main-list',
8 | templateUrl: './main-list.component.html',
9 | styleUrls: ['./main-list.component.scss'],
10 | providers: [ListService],
11 | })
12 | export class MainListComponent implements OnInit, OnDestroy {
13 |
14 | constructor(private listService: ListService,
15 | private router: Router) {
16 | }
17 |
18 | public list: any[] = []
19 | public page: number = 1
20 | public pagination: BehaviorSubject = new BehaviorSubject(1)
21 | private paginationSub: Subscription
22 |
23 |
24 | goNext(id: string): void {
25 | this.router.navigate(['/main/list', id])
26 | }
27 |
28 | loadMore(): void {
29 | this.pagination.next(this.pagination.getValue() + 1)
30 | }
31 |
32 | ngOnInit() {
33 | this.paginationSub = this.pagination
34 | .filter(page => page > 0)
35 | .switchMap(page => this.listService.getList(page))
36 | .subscribe(
37 | list => this.list.push(...list),
38 | err => Observable.of([]),
39 | )
40 | }
41 |
42 | ngOnDestroy() {
43 | this.paginationSub.unsubscribe()
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/app/main/main-menu/main-menu.component.html:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/src/app/main/main-menu/main-menu.component.scss:
--------------------------------------------------------------------------------
1 | .menu {
2 | width: 100%;
3 | height: 100%;
4 | background: #798298;
5 | -webkit-user-select: none;
6 | -moz-user-select: none;
7 | -ms-user-select: none;
8 | user-select: none;
9 | .user {
10 | width: 100%;
11 | height: auto;
12 | padding: 40px 15%;
13 | overflow: hidden;
14 | position: relative;
15 | > .user-photo {
16 | width: 70px;
17 | height: 70px;
18 | border-radius: 50%;
19 | float: left;
20 | cursor: pointer;
21 | }
22 | > .logout {
23 | float: right;
24 | display: inline-block;
25 | font-size: 18px;
26 | padding: 4px 11px;
27 | color: #e8e8e8;
28 | border: 1px solid transparent;
29 | transition: all .15s;
30 | transform: translate(-50%, -50%);
31 | border-radius: 2px;
32 | position: absolute;
33 | top: 50%;
34 | left: 65%;
35 | -webkit-user-select: none;
36 | -moz-user-select: none;
37 | -ms-user-select: none;
38 | user-select: none;
39 | cursor: pointer;
40 | &:hover {
41 | border: 1px solid #e8e8e8;
42 | }
43 | }
44 | }
45 | .list {
46 | width: 100%;
47 | height: auto;
48 | padding-top: 20px;
49 | > li {
50 | font-size: 21px;
51 | color: #fff;
52 | padding-left: 15%;
53 | line-height: 55px;
54 | letter-spacing: 3px;
55 | cursor: pointer;
56 | transition: all 0.2s;
57 | &.active {
58 | background: #949db3;
59 | }
60 | &:hover {
61 | background: #949db3;
62 | }
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/app/main/main-menu/main-menu.component.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'
3 |
4 | import { MainMenuComponent } from './main-menu.component'
5 |
6 | describe('MainMenuComponent', () => {
7 | let component: MainMenuComponent
8 | let fixture: ComponentFixture
9 |
10 | beforeEach(async(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [MainMenuComponent],
13 | })
14 | .compileComponents()
15 | }))
16 |
17 | beforeEach(() => {
18 | fixture = TestBed.createComponent(MainMenuComponent)
19 | component = fixture.componentInstance
20 | fixture.detectChanges()
21 | })
22 |
23 | it('should create', () => {
24 | expect(component)
25 | .toBeTruthy()
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/src/app/main/main-menu/main-menu.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core'
2 | import { MenuService } from './menu.service'
3 | import { Router } from '@angular/router'
4 |
5 | @Component({
6 | selector: 'app-main-menu',
7 | templateUrl: './main-menu.component.html',
8 | styleUrls: ['./main-menu.component.scss'],
9 | providers: [MenuService],
10 | })
11 | export class MainMenuComponent {
12 |
13 | constructor(private menuService: MenuService,
14 | private router: Router) {
15 | }
16 |
17 | logout(): void {
18 | this.menuService.logout()
19 | .subscribe(
20 | res => {
21 | this.router.navigate(['/login'])
22 | },
23 | )
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/main/main-menu/menu.service.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 |
3 | import { inject, TestBed } from '@angular/core/testing'
4 | import { MenuService } from './menu.service'
5 |
6 | describe('MenuService', () => {
7 | beforeEach(() => {
8 | TestBed.configureTestingModule({
9 | providers: [MenuService],
10 | })
11 | })
12 |
13 | it('should ...', inject([MenuService], (service: MenuService) => {
14 | expect(service)
15 | .toBeTruthy()
16 | }))
17 | })
18 |
--------------------------------------------------------------------------------
/src/app/main/main-menu/menu.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core'
2 | import { IpcRendererService } from '../../shared/service/ipcRenderer'
3 | import { Observable } from 'rxjs/Observable'
4 | import 'rxjs/Rx'
5 |
6 | @Injectable()
7 | export class MenuService {
8 |
9 | constructor(private ipcRendererService: IpcRendererService) {
10 | }
11 |
12 | logout(): Observable {
13 | return Observable.fromPromise(this.ipcRendererService.api('logout'))
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/main/main.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/app/main/main.component.scss:
--------------------------------------------------------------------------------
1 |
2 | .app-body {
3 | height: 100%;
4 | flex: 1;
5 | display: flex;
6 | flex-flow: row;
7 | > .app-list {
8 | position: fixed;
9 | top: 0;
10 | left: 0;
11 | bottom: 0;
12 | width: 260px;
13 | z-index: 10;
14 | }
15 | > .app-content {
16 | flex: 1;
17 | padding-left: 260px;
18 | overflow-x: hidden;
19 | height: 100%;
20 | background: #FCFCFC;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/main/main.component.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'
3 |
4 | import { MainComponent } from './main.component'
5 |
6 | describe('MainComponent', () => {
7 | let component: MainComponent
8 | let fixture: ComponentFixture
9 |
10 | beforeEach(async(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [MainComponent],
13 | })
14 | .compileComponents()
15 | }))
16 |
17 | beforeEach(() => {
18 | fixture = TestBed.createComponent(MainComponent)
19 | component = fixture.componentInstance
20 | fixture.detectChanges()
21 | })
22 |
23 | it('should create', () => {
24 | expect(component)
25 | .toBeTruthy()
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/src/app/main/main.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core'
2 |
3 | @Component({
4 | selector: 'app-main',
5 | templateUrl: './main.component.html',
6 | styleUrls: ['./main.component.scss'],
7 | })
8 | export class MainComponent {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/main/main.module.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/21.
3 | */
4 | import { CommonModule } from '@angular/common'
5 | import { NgModule } from '@angular/core'
6 | import { FormsModule } from '@angular/forms'
7 | import { MainRoutingModule } from './main.routing'
8 |
9 | import { SharedModule } from '../shared/shared.module'
10 |
11 | import { MainComponent } from './main.component'
12 | import { MainListComponent } from './main-list/main-list.component'
13 | import { MainDetailComponent } from './main-detail/main-detail.component'
14 | import { MainMenuComponent } from './main-menu/main-menu.component'
15 | import { SanitizePipe } from '../shared/pipe/sanitize'
16 |
17 | @NgModule({
18 | declarations: [
19 | MainComponent,
20 | MainListComponent,
21 | MainDetailComponent,
22 | MainMenuComponent,
23 | ],
24 | imports: [
25 | CommonModule,
26 | FormsModule,
27 | SharedModule,
28 | MainRoutingModule,
29 | ],
30 | exports: [MainComponent],
31 | providers: [
32 | SanitizePipe,
33 | ],
34 | })
35 | export class MainModule {
36 | }
37 |
--------------------------------------------------------------------------------
/src/app/main/main.routing.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/21.
3 | */
4 | import { NgModule } from '@angular/core'
5 | import { RouterModule, Routes } from '@angular/router'
6 |
7 | import { MainComponent } from './main.component'
8 | import { MainListComponent } from './main-list/main-list.component'
9 | import { MainDetailComponent } from './main-detail/main-detail.component'
10 |
11 |
12 | export const mainRoutes: Routes = [{
13 | path: '', component: MainComponent,
14 | children: [{
15 | path: '', redirectTo: 'list', pathMatch: 'full',
16 | }, {
17 | path: 'list', component: MainListComponent,
18 | }, {
19 | path: 'list/:id', component: MainDetailComponent,
20 | }],
21 | }]
22 |
23 | @NgModule({
24 | imports: [RouterModule.forChild(mainRoutes)],
25 | exports: [RouterModule],
26 | })
27 | export class MainRoutingModule {
28 | }
29 |
--------------------------------------------------------------------------------
/src/app/shared/component/back/back.component.html:
--------------------------------------------------------------------------------
1 |
2 | 返回列表
3 |
4 |
--------------------------------------------------------------------------------
/src/app/shared/component/back/back.component.scss:
--------------------------------------------------------------------------------
1 | .back {
2 | position: fixed;
3 | top: 10px;
4 | width: auto;
5 | height: auto;
6 | -webkit-user-select: none;
7 | -moz-user-select: none;
8 | -ms-user-select: none;
9 | user-select: none;
10 | cursor: pointer;
11 | background: rgba(121, 130, 152, 0.88);
12 | transition: all .15s;
13 | box-shadow: 0 5px 10px rgba(0, 0, 0, .1);
14 | padding: 8px 18px;
15 | border-radius: 3px;
16 | color: #fff;
17 | &:hover {
18 | background: rgba(121, 130, 152, 1);
19 | box-shadow: 0 5px 10px rgba(0, 0, 0, .25);
20 | }
21 | > span {
22 | font-size: 16px;
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/app/shared/component/back/back.component.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'
3 |
4 | import { BackComponent } from './back.component'
5 |
6 | describe('BackComponent', () => {
7 | let component: BackComponent
8 | let fixture: ComponentFixture
9 |
10 | beforeEach(async(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [BackComponent],
13 | })
14 | .compileComponents()
15 | }))
16 |
17 | beforeEach(() => {
18 | fixture = TestBed.createComponent(BackComponent)
19 | component = fixture.componentInstance
20 | fixture.detectChanges()
21 | })
22 |
23 | it('should create', () => {
24 | expect(component)
25 | .toBeTruthy()
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/src/app/shared/component/back/back.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core'
2 | import { Location } from '@angular/common'
3 |
4 | @Component({
5 | selector: 'app-back',
6 | templateUrl: './back.component.html',
7 | styleUrls: ['./back.component.scss'],
8 | })
9 | export class BackComponent {
10 |
11 | constructor(private location: Location) {
12 | }
13 |
14 | private returnOnlyOnce: boolean = true
15 |
16 | goBack(): void {
17 | if (!this.returnOnlyOnce) return
18 | this.location.back()
19 | this.returnOnlyOnce = false
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/shared/component/back/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/27.
3 | */
4 |
5 | export { BackComponent } from './back.component'
6 |
--------------------------------------------------------------------------------
/src/app/shared/directive/back/back.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, HostListener } from '@angular/core'
2 | import { Location } from '@angular/common'
3 |
4 | @Directive({
5 | selector: '[routeBack]',
6 | })
7 | export class BackDirective {
8 |
9 | constructor(private location: Location) {
10 | }
11 |
12 | @HostListener('click')
13 | goBack() {
14 | this.location.back()
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/shared/directive/back/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/3/1.
3 | */
4 |
5 | export { BackDirective } from './back.directive'
6 |
--------------------------------------------------------------------------------
/src/app/shared/pipe/sanitize/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/21.
3 | */
4 |
5 | export {SanitizePipe} from './sanitize.pipe'
--------------------------------------------------------------------------------
/src/app/shared/pipe/sanitize/sanitize.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core'
2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
3 |
4 |
5 | @Pipe({
6 | name: 'sanitize',
7 | })
8 | export class SanitizePipe implements PipeTransform {
9 |
10 | constructor(private domSanitizer: DomSanitizer) {
11 | }
12 |
13 | transform(value: any, args?: any): SafeHtml {
14 | return this.domSanitizer.bypassSecurityTrustHtml(value)
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/shared/service/ipcRenderer/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/12.
3 | */
4 |
5 | export { IpcRendererService } from './ipc-renderer.service'
6 |
--------------------------------------------------------------------------------
/src/app/shared/service/ipcRenderer/ipc-renderer.service.spec.ts:
--------------------------------------------------------------------------------
1 | /* tslint:disable:no-unused-variable */
2 |
3 | import { inject, TestBed } from '@angular/core/testing'
4 | import { IpcRendererService } from './ipc-renderer.service'
5 |
6 | describe('IpcRendererService', () => {
7 | beforeEach(() => {
8 | TestBed.configureTestingModule({
9 | providers: [IpcRendererService],
10 | })
11 | })
12 |
13 | it('should ...', inject([IpcRendererService], (service: IpcRendererService) => {
14 | expect(service)
15 | .toBeTruthy()
16 | }))
17 | })
18 |
--------------------------------------------------------------------------------
/src/app/shared/service/ipcRenderer/ipc-renderer.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core'
2 |
3 | declare let electron: any
4 |
5 | @Injectable()
6 | export class IpcRendererService {
7 |
8 | constructor() {
9 | }
10 |
11 | private ipcRenderer = electron.ipcRenderer
12 |
13 | on(message: string, done) {
14 | return this.ipcRenderer.on(message, done)
15 | }
16 |
17 | send(message: string, ...args) {
18 | this.ipcRenderer.send(message, args)
19 | }
20 |
21 | api(action: string, ...args) {
22 | this.ipcRenderer.send('api', action, ...args)
23 | return new Promise((resolve, reject) => {
24 | this.ipcRenderer.once(`${action}reply`, (e, reply, status) => {
25 | if (!reply) {
26 | return reject(status)
27 | }
28 | return resolve(reply)
29 | })
30 | })
31 | }
32 |
33 | dialog(action: string, ...args) {
34 | this.ipcRenderer.send('dialog', action, ...args)
35 | }
36 |
37 | sendSync(message: string, ...args) {
38 | return this.ipcRenderer.sendSync(message, arguments)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/app/shared/shared.module.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by WittBulter on 2017/2/21.
3 | */
4 | import { ModuleWithProviders, NgModule } from '@angular/core'
5 | import { CommonModule } from '@angular/common'
6 | import { FormsModule } from '@angular/forms'
7 |
8 | import { IpcRendererService } from './service/ipcRenderer'
9 | import { SanitizePipe } from './pipe/sanitize'
10 | import { BackDirective } from './directive/back/back.directive'
11 |
12 | @NgModule({
13 | imports: [
14 | CommonModule,
15 | FormsModule,
16 | ],
17 | declarations: [
18 | SanitizePipe,
19 | BackDirective,
20 | ],
21 | exports: [
22 | SanitizePipe,
23 | BackDirective,
24 | ],
25 | providers: [],
26 | })
27 |
28 | export class SharedModule {
29 | static forRoot(): ModuleWithProviders {
30 | return {
31 | ngModule: SharedModule,
32 | providers: [IpcRendererService],
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unix/news-feed/e9156630a2dbbddd67e73fa69383962253ad579b/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/assets/images/default_photo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unix/news-feed/e9156630a2dbbddd67e73fa69383962253ad579b/src/assets/images/default_photo.png
--------------------------------------------------------------------------------
/src/assets/images/timg.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unix/news-feed/e9156630a2dbbddd67e73fa69383962253ad579b/src/assets/images/timg.jpeg
--------------------------------------------------------------------------------
/src/assets/theme/_normalize.scss:
--------------------------------------------------------------------------------
1 | // _normalize
2 | // Remove default margin.
3 | //
4 | // HTML5 display definitions
5 | // ==========================================================================
6 | body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, button, textarea, p, blockquote, th, td {
7 | margin: 0;
8 | padding: 0;
9 | }
10 |
11 | //
12 | // Correct `block` display not defined for any HTML5 element in IE 8/9.
13 | // Correct `block` display not defined for `details` or `summary` in IE 10/11
14 | // and Firefox.
15 | // Correct `block` display not defined for `main` in IE 11.
16 | //
17 |
18 | article,
19 | aside,
20 | details,
21 | figcaption,
22 | figure,
23 | footer,
24 | header,
25 | hgroup,
26 | main,
27 | menu,
28 | nav,
29 | section,
30 | summary {
31 | display: block;
32 | }
33 |
34 | //
35 | // 1. Correct `inline-block` display not defined in IE 8/9.
36 | // 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
37 | //
38 |
39 | audio,
40 | canvas,
41 | progress,
42 | video {
43 | display: inline-block; // 1
44 | vertical-align: baseline; // 2
45 | }
46 |
47 | //
48 | // Prevent modern browsers from displaying `audio` without controls.
49 | // Remove excess height in iOS 5 devices.
50 | //
51 |
52 | audio:not([controls]) {
53 | display: none;
54 | height: 0;
55 | }
56 |
57 | //
58 | // Address `[hidden]` styling not present in IE 8/9/10.
59 | // Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
60 | //
61 |
62 | [hidden],
63 | template {
64 | display: none;
65 | }
66 |
67 | // Links
68 | // ==========================================================================
69 |
70 | //
71 | // Remove the gray background color from active links in IE 10.
72 | //
73 |
74 | a {
75 | background-color: transparent;
76 | }
77 |
78 | //
79 | // Improve readability of focused elements when they are also in an
80 | // active/hover state.
81 | //
82 |
83 | a:active,
84 | a:hover {
85 | outline: 0;
86 | }
87 |
88 | // Text-level semantics
89 | // ==========================================================================
90 |
91 | //
92 | // Address styling not present in IE 8/9/10/11, Safari, and Chrome.
93 | //
94 |
95 | abbr[title] {
96 | border-bottom: 1px dotted;
97 | }
98 |
99 | //
100 | // Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
101 | //
102 |
103 | b,
104 | strong {
105 | font-weight: bold;
106 | }
107 |
108 | //
109 | // Address styling not present in Safari and Chrome.
110 | //
111 |
112 | dfn {
113 | font-style: italic;
114 | }
115 |
116 | //
117 | // Address variable `h1` font-size and margin within `section` and `article`
118 | // contexts in Firefox 4+, Safari, and Chrome.
119 | //
120 | //
121 | //h1 {
122 | //font-size: 2em;
123 | //margin: 0.67em 0;
124 | //}
125 |
126 | //
127 | // Address styling not present in IE 8/9.
128 | //
129 |
130 | mark {
131 | background: #ff0;
132 | color: #000;
133 | }
134 |
135 | //
136 | // Address inconsistent and variable font size in all browsers.
137 | //
138 |
139 | small {
140 | font-size: 80%;
141 | }
142 |
143 | //
144 | // Prevent `sub` and `sup` affecting `line-height` in all browsers.
145 | //
146 |
147 | sub,
148 | sup {
149 | font-size: 75%;
150 | line-height: 0;
151 | position: relative;
152 | vertical-align: baseline;
153 | }
154 |
155 | sup {
156 | top: -0.5em;
157 | }
158 |
159 | sub {
160 | bottom: -0.25em;
161 | }
162 |
163 | // Embedded content
164 | // ==========================================================================
165 |
166 | //
167 | // Remove border when inside `a` element in IE 8/9/10.
168 | //
169 |
170 | img {
171 | border: 0;
172 | }
173 |
174 | //
175 | // Correct overflow not hidden in IE 9/10/11.
176 | //
177 |
178 | svg:not(:root) {
179 | overflow: hidden;
180 | }
181 |
182 | // Grouping content
183 | // ==========================================================================
184 |
185 | //
186 | // Address margin not present in IE 8/9 and Safari.
187 | //
188 |
189 | figure {
190 | margin: 1em 40px;
191 | }
192 |
193 | //
194 | // Address differences between Firefox and other browsers.
195 | //
196 |
197 | hr {
198 | box-sizing: content-box;
199 | height: 0;
200 | }
201 |
202 | //
203 | // Contain overflow in all browsers.
204 | //
205 |
206 | pre {
207 | overflow: auto;
208 | }
209 |
210 | //
211 | // Address odd `em`-unit font size rendering in all browsers.
212 | //
213 |
214 | code,
215 | kbd,
216 | pre,
217 | samp {
218 | font-family: monospace, monospace;
219 | font-size: 1em;
220 | }
221 |
222 | // Forms
223 | // ==========================================================================
224 |
225 | //
226 | // Known limitation: by default, Chrome and Safari on OS X allow very limited
227 | // styling of `select`, unless a `border` property is set.
228 | //
229 |
230 | //
231 | // 1. Correct color not being inherited.
232 | // Known issue: affects color of disabled elements.
233 | // 2. Correct font properties not being inherited.
234 | // 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
235 | //
236 |
237 | button,
238 | input,
239 | optgroup,
240 | select,
241 | textarea {
242 | color: inherit; // 1
243 | font: inherit; // 2
244 | margin: 0; // 3
245 | }
246 |
247 | //
248 | // Address `overflow` set to `hidden` in IE 8/9/10/11.
249 | //
250 |
251 | button {
252 | overflow: visible;
253 | }
254 |
255 | //
256 | // Address inconsistent `text-transform` inheritance for `button` and `select`.
257 | // All other form control elements do not inherit `text-transform` values.
258 | // Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
259 | // Correct `select` style inheritance in Firefox.
260 | //
261 |
262 | button,
263 | select {
264 | text-transform: none;
265 | }
266 |
267 | //
268 | // 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
269 | // and `video` controls.
270 | // 2. Correct inability to style clickable `input` types in iOS.
271 | // 3. Improve usability and consistency of cursor style between image-type
272 | // `input` and others.
273 | //
274 |
275 | button,
276 | html input[type="button"], // 1
277 | input[type="reset"],
278 | input[type="submit"] {
279 | -webkit-appearance: button; // 2
280 | cursor: pointer; // 3
281 | }
282 |
283 | //
284 | // Re-set default cursor for disabled elements.
285 | //
286 |
287 | button[disabled],
288 | html input[disabled] {
289 | cursor: default;
290 | }
291 |
292 | //
293 | // Remove inner padding and border in Firefox 4+.
294 | //
295 |
296 | button::-moz-focus-inner,
297 | input::-moz-focus-inner {
298 | border: 0;
299 | padding: 0;
300 | }
301 |
302 | //
303 | // Address Firefox 4+ setting `line-height` on `input` using `!important` in
304 | // the UA stylesheet.
305 | //
306 |
307 | input {
308 | line-height: normal;
309 | }
310 |
311 | //
312 | // It's recommended that you don't attempt to style these elements.
313 | // Firefox's implementation doesn't respect box-sizing, padding, or width.
314 | //
315 | // 1. Address box sizing set to `content-box` in IE 8/9/10.
316 | // 2. Remove excess padding in IE 8/9/10.
317 | //
318 |
319 | input[type="checkbox"],
320 | input[type="radio"] {
321 | box-sizing: border-box; // 1
322 | padding: 0; // 2
323 | }
324 |
325 | //
326 | // Fix the cursor style for Chrome's increment/decrement buttons. For certain
327 | // `font-size` values of the `input`, it causes the cursor style of the
328 | // decrement button to change from `default` to `text`.
329 | //
330 |
331 | input[type="number"]::-webkit-inner-spin-button,
332 | input[type="number"]::-webkit-outer-spin-button {
333 | height: auto;
334 | }
335 |
336 | //
337 | // 1. Address `appearance` set to `searchfield` in Safari and Chrome.
338 | // 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
339 | //
340 |
341 | input[type="search"] {
342 | -webkit-appearance: textfield; // 1
343 | box-sizing: content-box; //2
344 | }
345 |
346 | //
347 | // Remove inner padding and search cancel button in Safari and Chrome on OS X.
348 | // Safari (but not Chrome) clips the cancel button when the search input has
349 | // padding (and `textfield` appearance).
350 | //
351 |
352 | input[type="search"]::-webkit-search-cancel-button,
353 | input[type="search"]::-webkit-search-decoration {
354 | -webkit-appearance: none;
355 | }
356 |
357 | //
358 | // Define consistent border, margin, and padding.
359 | //
360 |
361 | fieldset {
362 | border: 1px solid #c0c0c0;
363 | margin: 0 2px;
364 | padding: 0.35em 0.625em 0.75em;
365 | }
366 |
367 | //
368 | // 1. Correct `color` not being inherited in IE 8/9/10/11.
369 | // 2. Remove padding so people aren't caught out if they zero out fieldsets.
370 | //
371 |
372 | legend {
373 | border: 0; // 1
374 | padding: 0; // 2
375 | }
376 |
377 | //
378 | // Remove default vertical scrollbar in IE 8/9/10/11.
379 | //
380 |
381 | textarea {
382 | overflow: auto;
383 | }
384 |
385 | //
386 | // Don't inherit the `font-weight` (applied by a rule above).
387 | // NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
388 | //
389 |
390 | optgroup {
391 | font-weight: bold;
392 | }
393 |
394 | // Tables
395 | // ==========================================================================
396 |
397 | //
398 | // Remove most spacing between table cells.
399 | //
400 |
401 | table {
402 | border-collapse: collapse;
403 | border-spacing: 0;
404 | }
405 |
406 | td,
407 | th {
408 | padding: 0;
409 | }
410 |
411 | //add
412 | ol, ul, li {
413 | list-style: none;
414 | }
415 |
--------------------------------------------------------------------------------
/src/assets/theme/_theme.scss:
--------------------------------------------------------------------------------
1 | .article-content {
2 | width: 100%;
3 | font-family: -apple-system, Helvetica Neue, Lantinghei SC, Open Sans, Arial, Hiragino Sans GB, Microsoft YaHei, Helvetica;
4 | p {
5 | font-weight: 400;
6 | line-height: 1.7;
7 | margin: 20px 0;
8 | }
9 | h1, h2, h3, h4, h5, h6 {
10 | font-weight: bold;
11 | }
12 |
13 | h1 {
14 | color: #000000;
15 | font-size: 28pt;
16 | margin: 45px 0 30px 0;
17 | }
18 |
19 | h2 {
20 | border-bottom: 1px solid #CCCCCC;
21 | color: #000000;
22 | font-size: 26px;
23 | margin: 45px 0 30px 0;
24 | }
25 |
26 | h3 {
27 | font-size: 22px;
28 | margin: 20px 0;
29 | }
30 |
31 | h4 {
32 | font-size: 18px;
33 | }
34 |
35 | h5 {
36 | font-size: 14px;
37 | }
38 |
39 | h6 {
40 | color: #777777;
41 | background-color: inherit;
42 | font-size: 14px;
43 | }
44 |
45 | hr {
46 | height: 0.2em;
47 | border: 0;
48 | color: #CCCCCC;
49 | background-color: #CCCCCC;
50 | }
51 |
52 | blockquote, dl, li, table, pre {
53 | margin: 15px 0;
54 | }
55 | ul, ol {
56 | margin: 35px 0;
57 | }
58 |
59 | a, a:visited {
60 | color: #4183C4;
61 | background-color: inherit;
62 | text-decoration: none;
63 | }
64 |
65 | blockquote {
66 | padding: 5px 10px;
67 | margin-bottom: 25px;
68 | background-color: #f7f7f7;
69 | border-left: 6px solid #b4b4b4;
70 | word-break: break-word;
71 | font-size: 16px;
72 | font-weight: 400;
73 | line-height: 25px;
74 | }
75 | ol {
76 | padding-left: 25px;
77 | list-style-type: decimal;
78 | > li {
79 | list-style: decimal;
80 |
81 | }
82 | }
83 | ul {
84 | padding-left: 25px;
85 | list-style-type: disc;
86 | > li {
87 | list-style: disc;
88 |
89 | }
90 | }
91 | img {
92 | display: block;
93 | margin: 30px auto;
94 | max-width: 90%;
95 | border-radius: 2px;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/assets/theme/main.scss:
--------------------------------------------------------------------------------
1 | @import "./normalize";
2 | @import "./theme";
3 |
4 | * {
5 | -webkit-box-sizing: border-box;
6 | -moz-box-sizing: border-box;
7 | box-sizing: border-box;
8 | }
9 |
10 | *:before,
11 | *:after {
12 | -webkit-box-sizing: border-box;
13 | -moz-box-sizing: border-box;
14 | box-sizing: border-box;
15 | }
16 |
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | }
4 |
--------------------------------------------------------------------------------
/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/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/unix/news-feed/e9156630a2dbbddd67e73fa69383962253ad579b/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Renderer
6 |
7 |
8 |
9 |
10 |
11 |
12 | Loading...
13 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/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/app.module'
7 |
8 | if (environment.production) {
9 | enableProdMode()
10 | }
11 |
12 | platformBrowserDynamic()
13 | .bootstrapModule(AppModule)
14 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | // This file includes polyfills needed by Angular 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'
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
3 | html, body {
4 | width: 100%;
5 | height: 100%;
6 | }
7 | html {
8 | -ms-text-size-adjust: 100%;
9 | -webkit-text-size-adjust: 100%;
10 | background: #FCFCFC;
11 | }
12 | body {
13 | font-family: -apple-system, 'Helvetica Neue', 'Lantinghei SC', 'Open Sans', Arial, 'Hiragino Sans GB','Microsoft YaHei',Helvetica;
14 | font-size: 16px;
15 | line-height: 1.4;
16 | -webkit-font-smoothing: antialiased;
17 | text-size-adjust: 100%;
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/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 | import { getTestBed } from '@angular/core/testing'
10 | import {
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting,
13 | } from '@angular/platform-browser-dynamic/testing'
14 |
15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
16 | declare var __karma__: any
17 | declare var require: any
18 |
19 | // Prevent Karma from running prematurely.
20 | __karma__.loaded = function() {
21 | }
22 |
23 | // First, initialize the Angular testing environment.
24 | getTestBed()
25 | .initTestEnvironment(
26 | BrowserDynamicTestingModule,
27 | platformBrowserDynamicTesting(),
28 | )
29 | // Then we find all the tests.
30 | const context = require.context('./', true, /\.spec\.ts$/)
31 | // And load the modules.
32 | context.keys()
33 | .map(context)
34 | // Finally, start Karma to run the tests.
35 | __karma__.start()
36 |
--------------------------------------------------------------------------------
/src/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "",
4 | "declaration": false,
5 | "emitDecoratorMetadata": true,
6 | "experimentalDecorators": true,
7 | "lib": ["es6", "dom"],
8 | "mapRoot": "./",
9 | "module": "es6",
10 | "moduleResolution": "node",
11 | "outDir": "../dist/out-tsc",
12 | "sourceMap": true,
13 | "target": "es5",
14 | "typeRoots": [
15 | "../node_modules/@types"
16 | ]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "callable-types": true,
7 | "class-name": true,
8 | "comment-format": [
9 | true,
10 | "check-space"
11 | ],
12 | "curly": true,
13 | "eofline": true,
14 | "forin": true,
15 | "import-blacklist": [true, "rxjs"],
16 | "import-spacing": true,
17 | "indent": [
18 | true,
19 | "spaces"
20 | ],
21 | "interface-over-type-literal": true,
22 | "label-position": true,
23 | "max-line-length": [
24 | true,
25 | 140
26 | ],
27 | "member-access": false,
28 | "member-ordering": [
29 | true,
30 | "static-before-instance",
31 | "variables-before-functions"
32 | ],
33 | "no-arg": true,
34 | "no-bitwise": true,
35 | "no-console": [
36 | true,
37 | "debug",
38 | "info",
39 | "time",
40 | "timeEnd",
41 | "trace"
42 | ],
43 | "no-construct": true,
44 | "no-debugger": true,
45 | "no-duplicate-variable": true,
46 | "no-empty": false,
47 | "no-empty-interface": true,
48 | "no-eval": true,
49 | "no-inferrable-types": true,
50 | "no-shadowed-variable": true,
51 | "no-string-literal": false,
52 | "no-string-throw": true,
53 | "no-switch-case-fall-through": true,
54 | "no-trailing-whitespace": true,
55 | "no-unused-expression": true,
56 | "no-use-before-declare": true,
57 | "no-var-keyword": true,
58 | "object-literal-sort-keys": false,
59 | "one-line": [
60 | true,
61 | "check-open-brace",
62 | "check-catch",
63 | "check-else",
64 | "check-whitespace"
65 | ],
66 | "prefer-const": true,
67 | "quotemark": [
68 | true,
69 | "single"
70 | ],
71 | "radix": true,
72 | "semicolon": [
73 | "always"
74 | ],
75 | "triple-equals": [
76 | true,
77 | "allow-null-check"
78 | ],
79 | "typedef-whitespace": [
80 | true,
81 | {
82 | "call-signature": "nospace",
83 | "index-signature": "nospace",
84 | "parameter": "nospace",
85 | "property-declaration": "nospace",
86 | "variable-declaration": "nospace"
87 | }
88 | ],
89 | "typeof-compare": true,
90 | "unified-signatures": true,
91 | "variable-name": false,
92 | "whitespace": [
93 | true,
94 | "check-branch",
95 | "check-decl",
96 | "check-operator",
97 | "check-separator",
98 | "check-type"
99 | ],
100 |
101 | "directive-selector": [true, "attribute", "app", "camelCase"],
102 | "component-selector": [true, "element", "app", "kebab-case"],
103 | "use-input-property-decorator": true,
104 | "use-output-property-decorator": true,
105 | "use-host-property-decorator": true,
106 | "no-input-rename": true,
107 | "no-output-rename": true,
108 | "use-life-cycle-interface": true,
109 | "use-pipe-transform-interface": true,
110 | "component-class-suffix": true,
111 | "directive-class-suffix": true,
112 | "no-access-missing-member": true,
113 | "templates-use-public": true,
114 | "invoke-injectable": true
115 | }
116 | }
117 |
--------------------------------------------------------------------------------