├── .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 |
2 |
3 | 4 |
5 |
6 | 14 | 15 |
16 |
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 |
    2 |
  • 3 |

    {{item.title}}

    4 |
    5 | {{item.description | slice: 0: 200}} 6 |
    7 |
  • 8 |
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 |
4 | 5 |
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 | --------------------------------------------------------------------------------