├── .angular-cli.json ├── .angulardoc.json ├── .editorconfig ├── .gitignore ├── .vscode └── launch.json ├── README.md ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.e2e.json ├── karma.conf.js ├── package-lock.json ├── package.json ├── protractor.conf.js ├── proxy.config.json ├── server ├── .babelrc ├── package-lock.json ├── package.json ├── src │ ├── app.js │ ├── config │ │ ├── development.js │ │ ├── index.js │ │ └── production.js │ ├── models │ │ ├── db.js │ │ ├── index.js │ │ └── schema.js │ ├── routes │ │ └── index.js │ └── users │ │ └── index.js └── yarn.lock ├── src ├── app │ ├── animations │ │ └── global-router-animation.ts │ ├── app-routing.module.ts │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── component │ │ ├── button │ │ │ ├── button.component.html │ │ │ ├── button.component.scss │ │ │ ├── button.component.spec.ts │ │ │ └── button.component.ts │ │ └── dropdown │ │ │ ├── dropdown.component.html │ │ │ ├── dropdown.component.scss │ │ │ ├── dropdown.component.spec.ts │ │ │ └── dropdown.component.ts │ ├── directives │ │ └── markdown-editor │ │ │ ├── markdown-editor.directive.spec.ts │ │ │ ├── markdown-editor.directive.ts │ │ │ └── simplemde.min.scss │ ├── interceptor │ │ └── global-response-interceptor.ts │ ├── page │ │ ├── add-link-note │ │ │ ├── add-link-note.component.html │ │ │ ├── add-link-note.component.scss │ │ │ ├── add-link-note.component.spec.ts │ │ │ └── add-link-note.component.ts │ │ ├── add-note │ │ │ ├── add-note.component.html │ │ │ ├── add-note.component.scss │ │ │ ├── add-note.component.spec.ts │ │ │ └── add-note.component.ts │ │ ├── classification │ │ │ ├── classification.component.html │ │ │ ├── classification.component.scss │ │ │ ├── classification.component.spec.ts │ │ │ └── classification.component.ts │ │ ├── edit-note │ │ │ ├── edit-note.component.html │ │ │ ├── edit-note.component.scss │ │ │ ├── edit-note.component.spec.ts │ │ │ └── edit-note.component.ts │ │ ├── index │ │ │ ├── index.component.html │ │ │ ├── index.component.scss │ │ │ ├── index.component.spec.ts │ │ │ └── index.component.ts │ │ ├── search │ │ │ ├── search.component.html │ │ │ ├── search.component.scss │ │ │ ├── search.component.spec.ts │ │ │ └── search.component.ts │ │ ├── tag │ │ │ ├── tag.component.html │ │ │ ├── tag.component.scss │ │ │ ├── tag.component.spec.ts │ │ │ └── tag.component.ts │ │ └── view-note │ │ │ ├── view-note.component.html │ │ │ ├── view-note.component.scss │ │ │ ├── view-note.component.spec.ts │ │ │ └── view-note.component.ts │ └── services │ │ ├── loading-bar │ │ ├── loading-bar.service.spec.ts │ │ └── loading-bar.service.ts │ │ ├── msg │ │ ├── msg.service.spec.ts │ │ └── msg.service.ts │ │ ├── note │ │ ├── note.service.spec.ts │ │ └── note.service.ts │ │ └── tag │ │ ├── tag.service.spec.ts │ │ └── tag.service.ts ├── assets │ ├── .gitkeep │ ├── fonts │ │ ├── ionicons.eot │ │ ├── ionicons.svg │ │ ├── ionicons.ttf │ │ └── ionicons.woff │ ├── index.svg │ ├── ionicons.min.css │ └── logo.svg ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.scss ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── typings.d.ts ├── tsconfig.json └── tslint.json /.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "micro-note" 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 | "polyfills": "polyfills.ts", 17 | "test": "test.ts", 18 | "tsconfig": "tsconfig.app.json", 19 | "testTsconfig": "tsconfig.spec.json", 20 | "prefix": "app", 21 | "styles": [ 22 | "styles.scss" 23 | ], 24 | "scripts": [], 25 | "environmentSource": "environments/environment.ts", 26 | "environments": { 27 | "dev": "environments/environment.ts", 28 | "prod": "environments/environment.prod.ts" 29 | } 30 | } 31 | ], 32 | "e2e": { 33 | "protractor": { 34 | "config": "./protractor.conf.js" 35 | } 36 | }, 37 | "lint": [ 38 | { 39 | "project": "src/tsconfig.app.json", 40 | "exclude": "**/node_modules/**" 41 | }, 42 | { 43 | "project": "src/tsconfig.spec.json", 44 | "exclude": "**/node_modules/**" 45 | }, 46 | { 47 | "project": "e2e/tsconfig.e2e.json", 48 | "exclude": "**/node_modules/**" 49 | } 50 | ], 51 | "test": { 52 | "karma": { 53 | "config": "./karma.conf.js" 54 | } 55 | }, 56 | "defaults": { 57 | "styleExt": "scss", 58 | "component": { 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.angulardoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "repoId": "0750bde0-9099-4e20-a314-ec2df01664e5", 3 | "lastSync": 1505296448645 4 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | *.suo 11 | *.ntvs* 12 | *.njsproj 13 | *.sln 14 | .vscode/angulardoc.json 15 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceRoot}/server/src/app.js" 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 笔记文章管理应用 [说明](https://lweiwei.com/view/59b5f6947ee00a2998057748) 2 | 3 | 基于`Angular4`+`koa2`实现的一个自动生成笔记文章的应用管理程序,在线预览[地址](http://lweiwei.com:8000) 4 | ### 开发 5 | (本地要安装mongodb,并且启动mongodb服务) 6 | 7 | ``` bash 8 | # git clone https://github.com/linguowei/micro-note.git 9 | # npm install 或者 yarn install 注意不要用cnpm!! 10 | # npm run start 11 | # cd /server // server文件夹是该应用的后端代码 12 | # npm install 或者 yarn install 注意不要用cnpm!! 13 | # npm run dev 14 | # 浏览器访问 localhost:4200 15 | ``` 16 | ### 部署 17 | ``` bash 18 | # git clone https://github.com/linguowei/micro-note.git 19 | # npm install 或者 yarn install 注意不要用cnpm!! 20 | # npm run build 21 | # 1. 把build出来的文件夹dist和server文件夹拷贝到服务器放在一个目录里面 22 | # 2. 服务器安装node、mongodb、pm2 23 | # 3. 启动mongodb服务,进入server文件夹执行npm install, 然后执行npm run pro 24 | ``` 25 | ### License 26 | [GPL 禁止商用](https://www.oschina.net/question/54100_9455) 27 | -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { MicroNotePage } from './app.po'; 2 | 3 | describe('micro-note App', () => { 4 | let page: MicroNotePage; 5 | 6 | beforeEach(() => { 7 | page = new MicroNotePage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class MicroNotePage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /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-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | angularCli: { 23 | environment: 'dev' 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | singleRun: false 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "micro-note", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve --proxy-config proxy.config.json --port 4300", 8 | "build": "ng build --prod", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "^4.0.0", 16 | "@angular/common": "^4.0.0", 17 | "@angular/compiler": "^4.0.0", 18 | "@angular/core": "^4.0.0", 19 | "@angular/forms": "^4.0.0", 20 | "@angular/http": "^4.0.0", 21 | "@angular/platform-browser": "^4.0.0", 22 | "@angular/platform-browser-dynamic": "^4.0.0", 23 | "@angular/router": "^4.0.0", 24 | "core-js": "^2.4.1", 25 | "highlight.js": "^9.12.0", 26 | "marked": "^0.3.6", 27 | "rxjs": "^5.4.1", 28 | "simplemde": "^1.11.2", 29 | "zone.js": "^0.8.14" 30 | }, 31 | "devDependencies": { 32 | "@angular/cli": "1.2.7", 33 | "@angular/compiler-cli": "^4.0.0", 34 | "@angular/language-service": "^4.0.0", 35 | "@types/jasmine": "~2.5.53", 36 | "@types/jasminewd2": "~2.0.2", 37 | "@types/node": "~6.0.60", 38 | "codelyzer": "~3.0.1", 39 | "jasmine-core": "~2.6.2", 40 | "jasmine-spec-reporter": "~4.1.0", 41 | "karma": "~1.7.0", 42 | "karma-chrome-launcher": "~2.1.1", 43 | "karma-cli": "~1.0.1", 44 | "karma-coverage-istanbul-reporter": "^1.2.1", 45 | "karma-jasmine": "~1.1.0", 46 | "karma-jasmine-html-reporter": "^0.2.2", 47 | "protractor": "~5.1.2", 48 | "ts-node": "~3.0.4", 49 | "tslint": "~5.3.2", 50 | "typescript": "~2.3.3" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /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 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /proxy.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api":{ 3 | "target":"http://localhost:3002" 4 | } 5 | } -------------------------------------------------------------------------------- /server/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-0" 5 | ], 6 | "plugins": [] 7 | } -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "micro-note-server", 3 | "version": "1.0.0", 4 | "description": "this is micro-note-server", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "NODE_ENV=development nodemon --exec babel-node ./src/app.js", 9 | "pro": "NODE_ENV=production pm2 start ./src/app.js" 10 | }, 11 | "author": "1368033036@qq.com", 12 | "license": "ISC", 13 | "dependencies": { 14 | "bluebird": "^3.5.0", 15 | "koa": "^2.3.0", 16 | "koa-bodyparser": "^4.2.0", 17 | "koa-cors": "0.0.16", 18 | "koa-generic-session-mongo": "^0.4.0", 19 | "koa-router": "^7.2.1", 20 | "koa-session-minimal": "^3.0.4", 21 | "koa-static-cache": "^5.1.1", 22 | "mongoose": "^4.11.7", 23 | "node-readability": "^3.0.0", 24 | "phantom": "^4.0.5" 25 | }, 26 | "devDependencies": { 27 | "babel-core": "^6.25.0", 28 | "babel-preset-es2015": "^6.24.1", 29 | "babel-preset-stage-0": "^6.24.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /server/src/app.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa') 2 | const bodyParser = require('koa-bodyparser') 3 | const router = require('./routes/index') 4 | const cors = require('koa-cors') 5 | const path = require('path') 6 | const staticCache = require('koa-static-cache') 7 | const config = require('./config/index') 8 | const session = require('koa-session-minimal') 9 | const MongoStore = require('koa-generic-session-mongo') 10 | 11 | const resolve = file => path.resolve(__dirname, file) 12 | const app = new Koa() 13 | 14 | app.use(staticCache({ 15 | dir: resolve('../../dist'), 16 | maxAge: 365 * 24 * 60 * 60, 17 | gzip: true 18 | })) 19 | 20 | let cookie = { 21 | maxAge: 5 * 60 * 1000, // cookie有效时长 22 | expires: '', // cookie失效时间 23 | path: '', // 写cookie所在的路径 24 | domain: '', // 写cookie所在的域名 25 | httpOnly: true, // 是否只用于http请求中获取 26 | overwrite: '', // 是否允许重写 27 | secure: '', 28 | sameSite: '', 29 | signed: '', 30 | } 31 | app.use(session({ 32 | key: 'SESSION_ID', 33 | store: new MongoStore({}), 34 | cookie: cookie 35 | })) 36 | 37 | app.use(cors()) 38 | app.use(bodyParser()) 39 | app.use(router.routes()) 40 | 41 | app.listen(config.port || 3001, ()=> { 42 | console.log('监听端口' + config.port || 3001) 43 | console.log("环境变量是" + process.env.NODE_ENV); 44 | }) -------------------------------------------------------------------------------- /server/src/config/development.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 开发环境的配置内容 3 | */ 4 | 5 | module.exports = { 6 | env: 'development', //环境名称 7 | port: 3002, //服务端口号 8 | mongodb_url: 'mongodb://localhost:27017/microDev', //数据库地址 9 | } -------------------------------------------------------------------------------- /server/src/config/index.js: -------------------------------------------------------------------------------- 1 | const development_env = require('./development'); 2 | const production_env = require('./production'); 3 | 4 | //根据不同的NODE_ENV,输出不同的配置对象,默认输出development的配置对象 5 | module.exports = { 6 | development: development_env, 7 | production: production_env 8 | }[process.env.NODE_ENV || 'development'] 9 | -------------------------------------------------------------------------------- /server/src/config/production.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 生产环境的配置内容 3 | */ 4 | 5 | module.exports = { 6 | env: 'production', //环境名称 7 | port: 3001, //服务端口号 8 | mongodb_url: 'mongodb://localhost:27017/microPro', //数据库地址 9 | } -------------------------------------------------------------------------------- /server/src/models/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | mongoose.Promise = require('bluebird') 3 | const config = require('../config') 4 | 5 | mongoose.connect(config.mongodb_url,{ useMongoClient: true }) 6 | let db = mongoose.connection 7 | 8 | db.on('error', console.error.bind(console, '连接错误:')); 9 | db.once('open', function() { 10 | console.log(config.mongodb_url, '连接成功'); 11 | }) 12 | 13 | 14 | module.exports = mongoose -------------------------------------------------------------------------------- /server/src/models/index.js: -------------------------------------------------------------------------------- 1 | const models = require('./schema') 2 | 3 | module.exports = models -------------------------------------------------------------------------------- /server/src/models/schema.js: -------------------------------------------------------------------------------- 1 | const db = require('./db') 2 | 3 | const noteSchema = new db.Schema({ 4 | user_name: String, 5 | title: String, 6 | content: String, 7 | tag: Array, 8 | date: Date, 9 | sourceLink: String 10 | }) 11 | 12 | const tagSchema = new db.Schema({ 13 | user_name: String, 14 | name: String 15 | }) 16 | 17 | const Models = { 18 | NoteList: db.model('NoteList', noteSchema), 19 | TagList: db.model('TagList', tagSchema) 20 | } 21 | 22 | module.exports = Models -------------------------------------------------------------------------------- /server/src/routes/index.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router') 2 | const read = require('node-readability') 3 | const fs = require('fs') 4 | const path = require('path') 5 | const Models = require('../models') 6 | const phantom = require('phantom') 7 | const users = require('../users').items 8 | 9 | const resolve = file => path.resolve(__dirname, file) 10 | const router = new Router() 11 | 12 | // 定义成功的返回模板 13 | let successState = { 14 | msg: 'success', 15 | code: 200, 16 | data: '' 17 | } 18 | // 定义错误的返回模板 19 | let errorState = { 20 | msg: 'error', 21 | code: '', 22 | data: '' 23 | } 24 | 25 | const findUser = function(name, password){ 26 | return users.find(function(item){ 27 | return item.name === name && item.password === password; 28 | }); 29 | }; 30 | 31 | router.post('/api/login', async (ctx, next) => { 32 | let user = findUser(ctx.request.body.name, ctx.request.body.pwd); 33 | if(user){ 34 | let session = ctx.session 35 | session.isLogin = true 36 | session.userName = ctx.request.body.name 37 | successState.msg = '登录成功!' 38 | ctx.body = successState 39 | }else{ 40 | errorState.msg = '账号或密码错误!' 41 | errorState.code = 400 42 | ctx.body = errorState 43 | } 44 | }) 45 | 46 | router.get('/api/logout', async (ctx, next) => { 47 | ctx.session = null; 48 | let success = Object.assign({}, successState, { 49 | code: 200, 50 | msg: '退出登录成功!' 51 | }) 52 | ctx.response.body = success 53 | }) 54 | 55 | 56 | /** 57 | * 笔记 C、R、U、D 58 | */ 59 | 60 | router.get('/api/allNote', async (ctx, next) => { 61 | if ( ctx.session && ctx.session.isLogin && ctx.session.userName ) { 62 | await Models.NoteList.find({user_name: ctx.session.userName}, (err, docs) => { 63 | if(err){ 64 | ctx.throw(500) 65 | return 66 | } 67 | let success = Object.assign({}, successState, { 68 | data: docs.reverse() 69 | }) 70 | ctx.response.body = success 71 | ctx.sessionHandler.regenerateId() 72 | }) 73 | } else { 74 | let error = Object.assign({}, errorState, { 75 | code: 401, 76 | msg: 'Unauthorized' 77 | }) 78 | ctx.body = error 79 | } 80 | }) 81 | 82 | // 生成笔记 83 | router.post('/api/generateNote', async (ctx, next) => { 84 | if ( ctx.session && ctx.session.isLogin && ctx.session.userName ) { 85 | const instance = await phantom.create() 86 | const page = await instance.createPage() 87 | const status = await page.open(ctx.request.body.link) 88 | const content = await page.property('content') 89 | await instance.exit() 90 | await new Promise((resolve, reject) => { 91 | read(content, (err, article, meta) => { 92 | if (err) { 93 | reject(err) 94 | } else { 95 | resolve(article) 96 | } 97 | }) 98 | }).then((success) => { 99 | ctx.body = { 100 | title: success.title, 101 | content: success.content 102 | } 103 | }) 104 | }else{ 105 | let error = Object.assign({}, successState, { 106 | code: 401, 107 | msg: 'Unauthorized' 108 | }) 109 | ctx.body = error 110 | } 111 | }) 112 | 113 | // 新增笔记 114 | router.post('/api/addNote', async (ctx, next) => { 115 | if ( ctx.session && ctx.session.isLogin && ctx.session.userName ) { 116 | ctx.request.body['user_name'] = ctx.session.userName 117 | await new Models.NoteList(ctx.request.body).save((err, docs) => { 118 | if(err){ 119 | ctx.throw(500) 120 | return 121 | } 122 | successState.data = docs 123 | ctx.response.body = successState 124 | }) 125 | } else { 126 | let error = Object.assign({}, errorState, { 127 | code: 401, 128 | msg: 'Unauthorized' 129 | }) 130 | ctx.body = error 131 | } 132 | }) 133 | 134 | // 删除笔记 135 | router.post('/api/deleteNote', async (ctx, next ) => { 136 | if ( ctx.session && ctx.session.isLogin && ctx.session.userName ) { 137 | await Models.NoteList.remove({ 138 | _id: ctx.request.body.id, 139 | user_name: ctx.session.userName 140 | }, (err, docs) => { 141 | if(err){ 142 | ctx.throw(500) 143 | return 144 | } 145 | successState.data = docs 146 | ctx.response.body = successState 147 | }) 148 | } else { 149 | let error = Object.assign({}, errorState, { 150 | code: 401, 151 | msg: 'Unauthorized' 152 | }) 153 | ctx.body = error 154 | } 155 | }) 156 | 157 | // 修改笔记 158 | router.post('/api/modify', async (ctx, next) => { 159 | if ( ctx.session && ctx.session.isLogin && ctx.session.userName ) { 160 | await new Promise((resolve, reject) => { 161 | Models.NoteList.update({ 162 | _id: ctx.request.body._id, 163 | user_name: ctx.session.userName 164 | }, ctx.request.body, (err, docs) => { 165 | if(err){ 166 | reject(err) 167 | }else{ 168 | resolve(docs) 169 | } 170 | }) 171 | }).then((success) => { 172 | successState.data = ctx.request.body 173 | ctx.response.body = successState 174 | }) 175 | } else { 176 | let error = Object.assign({}, errorState, { 177 | code: 401, 178 | msg: 'Unauthorized' 179 | }) 180 | ctx.body = error 181 | } 182 | }) 183 | 184 | 185 | /** 186 | * 标签 C、R、U、D 187 | */ 188 | 189 | router.post('/api/addTag', async (ctx, next) => { 190 | if ( ctx.session && ctx.session.isLogin && ctx.session.userName ) { 191 | ctx.request.body['user_name'] = ctx.session.userName 192 | await new Models.TagList(ctx.request.body).save((err, docs) => { 193 | if(err){ 194 | ctx.throw(500) 195 | return 196 | } 197 | successState.data = docs 198 | ctx.response.body = successState 199 | }) 200 | } else { 201 | let error = Object.assign({}, errorState, { 202 | code: 401, 203 | msg: 'Unauthorized' 204 | }) 205 | ctx.body = error 206 | } 207 | }) 208 | 209 | router.post('/api/deleteTag', async (ctx, next) => { 210 | if ( ctx.session && ctx.session.isLogin && ctx.session.userName ) { 211 | await Models.TagList.remove({ 212 | _id: ctx.request.body.id, 213 | user_name: ctx.session.userName 214 | }, (err, docs) => { 215 | if(err){ 216 | ctx.throw(500) 217 | return 218 | } 219 | successState.data = docs 220 | ctx.response.body = successState 221 | }) 222 | } else { 223 | let error = Object.assign({}, errorState, { 224 | code: 401, 225 | msg: 'Unauthorized' 226 | }) 227 | ctx.body = error 228 | } 229 | }) 230 | 231 | router.get('/api/TagList', async (ctx, next) => { 232 | if ( ctx.session && ctx.session.isLogin && ctx.session.userName ) { 233 | await Models.TagList.find({user_name: ctx.session.userName}, (err, docs) => { 234 | if(err){ 235 | ctx.throw(500) 236 | return 237 | } 238 | let success = Object.assign({}, successState, { 239 | data: docs.reverse() 240 | }) 241 | ctx.response.body = success 242 | }) 243 | } else { 244 | let error = Object.assign({}, errorState, { 245 | code: 401, 246 | msg: 'Unauthorized' 247 | }) 248 | ctx.body = error 249 | } 250 | }) 251 | 252 | router.get('*', (ctx, next) => { 253 | const html = fs.readFileSync(resolve('../../../dist/' + 'index.html'), 'utf-8') 254 | ctx.body = html 255 | }) 256 | 257 | module.exports = router -------------------------------------------------------------------------------- /server/src/users/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | items: [ 3 | {name: 'test', password: '123456'}, 4 | {name: 'test2', password: '123456'}, 5 | ] 6 | } -------------------------------------------------------------------------------- /src/app/animations/global-router-animation.ts: -------------------------------------------------------------------------------- 1 | import { AnimationEntryMetadata, state } from '@angular/core'; 2 | import { trigger, transition, animate, style, query, group } from '@angular/animations'; 3 | 4 | export const routeAnimation: AnimationEntryMetadata = 5 | trigger('routeAnimation', [ 6 | transition(':enter', [ 7 | style({ 8 | position: 'absolute' 9 | }), 10 | animate('0.5s ease-in-out') 11 | ]), 12 | transition('* => *', [ 13 | query(':leave', style({ transform: 'translateX(0)', position: 'absolute'}), { optional: true }), 14 | query(':enter', style({ transform: 'translateX(100%)', position: 'absolute'}), { optional: true }), 15 | 16 | group([ 17 | query(':leave', animate('.5s ease-in-out', style({transform: 'translateX(-100%)'})), { optional: true }), 18 | query(':enter', animate('.5s ease-in-out', style({transform: 'translateX(0)'})), { optional: true }) 19 | ]) 20 | ]) 21 | ]); 22 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { EditNoteComponent } from './page/edit-note/edit-note.component'; 2 | import { ViewNoteComponent } from './page/view-note/view-note.component'; 3 | import { ClassificationComponent } from './page/classification/classification.component'; 4 | import { TagComponent } from './page/tag/tag.component'; 5 | import { SearchComponent } from './page/search/search.component'; 6 | import { AddLinkNoteComponent } from './page/add-link-note/add-link-note.component'; 7 | import { IndexComponent } from './page/index/index.component'; 8 | import { AppComponent } from './app.component'; 9 | import { AddNoteComponent } from './page/add-note/add-note.component'; 10 | import { NgModule } from '@angular/core'; 11 | import { Routes, RouterModule } from '@angular/router'; 12 | 13 | const routes: Routes = [ 14 | { 15 | path: 'index', 16 | component: IndexComponent 17 | }, 18 | { 19 | path: 'addNote', 20 | component: AddNoteComponent, 21 | children: [] 22 | }, 23 | { 24 | path: 'addLinkNote', 25 | component: AddLinkNoteComponent, 26 | children: [] 27 | }, 28 | { 29 | path: 'tag', 30 | component: TagComponent, 31 | children: [] 32 | }, 33 | { 34 | path: 'classification', 35 | component: ClassificationComponent, 36 | children: [] 37 | }, 38 | { 39 | path: 'search', 40 | component: SearchComponent, 41 | children: [] 42 | }, 43 | { 44 | path: 'viewNote', 45 | component: ViewNoteComponent, 46 | children: [] 47 | }, 48 | { 49 | path: 'editNote', 50 | component: EditNoteComponent, 51 | children: [] 52 | }, 53 | { 54 | path: '**', 55 | redirectTo: 'index', 56 | pathMatch: 'full' 57 | } 58 | ]; 59 | 60 | @NgModule({ 61 | imports: [RouterModule.forRoot(routes)], 62 | exports: [RouterModule] 63 | }) 64 | export class AppRoutingModule { } 65 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 49 |
50 |
51 | 52 |
-------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | .icon{ 2 | font-size: 22px; 3 | } 4 | .left-menu{ 5 | width: 60px; 6 | height: 100vh; 7 | box-shadow: 0 1px 6px #ccc; 8 | background-color: #fff; 9 | float: left; 10 | ul{ 11 | li{ 12 | text-align: center; 13 | padding: 15px 0; 14 | cursor: pointer; 15 | font-size: 12px; 16 | a{ 17 | color: #919191; 18 | &:hover{ 19 | color: #404040; 20 | } 21 | }; 22 | .router-active{ 23 | color: #404040; 24 | } 25 | } 26 | .personal-center{ 27 | width: 60px; 28 | position: absolute; 29 | bottom: 0; 30 | left: 0; 31 | a{ 32 | font-size: 26px; 33 | } 34 | } 35 | .personal-center-content{ 36 | width: 130px; 37 | height: 100px; 38 | position: absolute; 39 | bottom: 10px; 40 | left: 65px; 41 | background-color: #fff; 42 | box-shadow: 0 1px 6px #ccc; 43 | border-radius: 3px; 44 | z-index: 30; 45 | padding: 5px; 46 | .title{ 47 | height: 50px; 48 | border-bottom: 1px #ececec solid; 49 | i{ 50 | font-size: 20px; 51 | color: #71777c; 52 | } 53 | } 54 | .logout{ 55 | text-align: left; 56 | padding: 5px; 57 | font-size: 14px; 58 | color: #4a4a4a; 59 | margin-top: 10px; 60 | &:hover{ 61 | background-color: #3db8c1; 62 | color: #fff; 63 | } 64 | } 65 | } 66 | }; 67 | .logo{ 68 | border-bottom: 1px solid #ececec; 69 | } 70 | } 71 | .content-wrap{ 72 | overflow: hidden; 73 | height: 100vh; 74 | background-color: #f7f7f7; 75 | } -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | 4 | import { AppComponent } from './app.component'; 5 | 6 | describe('AppComponent', () => { 7 | beforeEach(async(() => { 8 | TestBed.configureTestingModule({ 9 | imports: [ 10 | RouterTestingModule 11 | ], 12 | declarations: [ 13 | AppComponent 14 | ], 15 | }).compileComponents(); 16 | })); 17 | 18 | it('should create the app', async(() => { 19 | const fixture = TestBed.createComponent(AppComponent); 20 | const app = fixture.debugElement.componentInstance; 21 | expect(app).toBeTruthy(); 22 | })); 23 | 24 | it(`should have as title 'app'`, async(() => { 25 | const fixture = TestBed.createComponent(AppComponent); 26 | const app = fixture.debugElement.componentInstance; 27 | expect(app.title).toEqual('app'); 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).toContain('Welcome to app!'); 35 | })); 36 | }); 37 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Router } from '@angular/router'; 2 | import { MsgService } from './services/msg/msg.service'; 3 | import { HttpClient } from '@angular/common/http'; 4 | import { Component, DoCheck } from '@angular/core'; 5 | import { routeAnimation } from './animations/global-router-animation'; 6 | 7 | @Component({ 8 | selector: 'app-root', 9 | templateUrl: './app.component.html', 10 | styleUrls: ['./app.component.scss'], 11 | host: { 12 | '(document:click)': 'documentClick($event)', 13 | } 14 | }) 15 | export class AppComponent implements DoCheck { 16 | isShowAccount = false; 17 | userName = ''; 18 | 19 | constructor( 20 | private http: HttpClient, 21 | private msg: MsgService, 22 | private router: Router 23 | ) { 24 | } 25 | 26 | ngDoCheck() { 27 | this.userName = localStorage.getItem('userName'); 28 | } 29 | 30 | personalCenter(e) { 31 | this.isShowAccount = !this.isShowAccount; 32 | e.stopPropagation(); 33 | } 34 | 35 | documentClick(e) { 36 | this.isShowAccount = false; 37 | e.stopPropagation(); 38 | } 39 | personalCenterContent(e) { 40 | e.stopPropagation(); 41 | } 42 | 43 | logout() { 44 | this.http.get('/api/logout').subscribe((res) => { 45 | if (res['code'] === 200) { 46 | this.msg.info(res['msg']); 47 | this.isShowAccount = false; 48 | setTimeout(() => { 49 | this.router.navigate(['/index']); 50 | }, 2000); 51 | } 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { GlobalResponseInterceptor } from './interceptor/global-response-interceptor'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; 4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 5 | import { FormsModule } from '@angular/forms'; 6 | import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http'; 7 | 8 | // customize module 9 | import { AppRoutingModule } from './app-routing.module'; 10 | 11 | // component 12 | import { AppComponent } from './app.component'; 13 | import { AddNoteComponent } from './page/add-note/add-note.component'; 14 | import { AddLinkNoteComponent, CalculationContentHeightDirective } from './page/add-link-note/add-link-note.component'; 15 | import { SearchComponent } from './page/search/search.component'; 16 | import { IndexComponent } from './page/index/index.component'; 17 | import { DropdownComponent } from './component/dropdown/dropdown.component'; 18 | import { ButtonComponent } from './component/button/button.component'; 19 | import { ClassificationComponent, ClassificationTabsContentHeightDirective, FilterNoteContentPipe } from './page/classification/classification.component'; 20 | import { ViewNoteComponent } from './page/view-note/view-note.component'; 21 | import { EditNoteComponent } from './page/edit-note/edit-note.component'; 22 | import { TagComponent } from './page/tag/tag.component'; 23 | 24 | // directive 25 | import { MarkdownEditorDirective } from './directives/markdown-editor/markdown-editor.directive'; 26 | 27 | // service 28 | import { LoadingBarService } from './services/loading-bar/loading-bar.service'; 29 | import { MsgService } from './services/msg/msg.service'; 30 | import { NoteService } from './services/note/note.service'; 31 | import { TagService } from './services/tag/tag.service'; 32 | 33 | @NgModule({ 34 | declarations: [ 35 | AppComponent, 36 | AddNoteComponent, 37 | AddLinkNoteComponent, 38 | SearchComponent, 39 | IndexComponent, 40 | MarkdownEditorDirective, 41 | CalculationContentHeightDirective, 42 | ClassificationTabsContentHeightDirective, 43 | DropdownComponent, 44 | ButtonComponent, 45 | TagComponent, 46 | ClassificationComponent, 47 | ViewNoteComponent, 48 | EditNoteComponent, 49 | FilterNoteContentPipe, 50 | ], 51 | imports: [ 52 | BrowserModule, 53 | HttpClientModule, // http模块 54 | FormsModule, // 表单模块 55 | AppRoutingModule, // 路由配置模块 56 | BrowserAnimationsModule, // 动画模块 57 | ], 58 | providers: [ 59 | { 60 | provide: HTTP_INTERCEPTORS, 61 | useClass: GlobalResponseInterceptor, 62 | multi: true, 63 | }, 64 | LoadingBarService, 65 | TagService, 66 | NoteService, 67 | MsgService, 68 | ], 69 | bootstrap: [AppComponent], // 根组件 70 | schemas: [CUSTOM_ELEMENTS_SCHEMA] 71 | }) 72 | export class AppModule { } 73 | -------------------------------------------------------------------------------- /src/app/component/button/button.component.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /src/app/component/button/button.component.scss: -------------------------------------------------------------------------------- 1 | .my-btn{ 2 | color: #495060; 3 | outline: 0; 4 | display: inline-block; 5 | margin-bottom: 0; 6 | font-weight: 400; 7 | text-align: center; 8 | cursor: pointer; 9 | border: 1px solid dddee1; 10 | white-space: nowrap; 11 | padding: 6px 15px; 12 | font-size: 12px; 13 | border-radius: 4px; 14 | background-color: transparent; 15 | border: 1px solid #ccc; 16 | vertical-align:middle 17 | } -------------------------------------------------------------------------------- /src/app/component/button/button.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ButtonComponent } from './button.component'; 4 | 5 | describe('ButtonComponent', () => { 6 | let component: ButtonComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ButtonComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ButtonComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/component/button/button.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-button', 5 | templateUrl: './button.component.html', 6 | styleUrls: ['./button.component.scss'] 7 | }) 8 | export class ButtonComponent implements OnInit { 9 | @Input() Icon: string; 10 | constructor() { 11 | } 12 | 13 | ngOnInit() { 14 | } 15 | 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/app/component/dropdown/dropdown.component.html: -------------------------------------------------------------------------------- 1 |
2 | 6 |
7 |
    8 |
  • 9 | {{item.name}} 10 |
  • 11 |
12 |
13 |
-------------------------------------------------------------------------------- /src/app/component/dropdown/dropdown.component.scss: -------------------------------------------------------------------------------- 1 | ::-webkit-scrollbar{ 2 | width: 0px; 3 | } 4 | .my-dropdown{ 5 | position: relative; 6 | width: 100%; 7 | min-width: 80px; 8 | } 9 | .my-dropdown-rel{ 10 | text-align: center; 11 | cursor: pointer; 12 | } 13 | .my-dropdown-menu{ 14 | position: absolute; 15 | top: 25px; 16 | z-index: 99; 17 | width: 100%; 18 | background-color: #fff; 19 | border-radius: 4px; 20 | box-shadow: 0 1px 6px rgba(0,0,0,.2); 21 | height: 0px; 22 | overflow: hidden; 23 | } 24 | .my-dropdown-menu ul{ 25 | padding: 4px 0; 26 | max-height: 192px; 27 | overflow: auto; 28 | } 29 | .my-dropdown-menu ul li{ 30 | text-align: center; 31 | padding: 7px 16px; 32 | color: #495060; 33 | cursor: pointer; 34 | } 35 | .my-dropdown-menu ul li:hover{ 36 | background: #f3f3f3; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/app/component/dropdown/dropdown.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DropdownComponent } from './dropdown.component'; 4 | 5 | describe('DropdownComponent', () => { 6 | let component: DropdownComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ DropdownComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DropdownComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/component/dropdown/dropdown.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; 2 | import { trigger, state, style, animate, transition } from '@angular/animations'; 3 | 4 | @Component({ 5 | selector: 'app-dropdown', 6 | templateUrl: './dropdown.component.html', 7 | styleUrls: ['./dropdown.component.scss'], 8 | animations: [ 9 | trigger('dropdownMenuState', [ 10 | state('inactive', style({ 11 | height: '0' 12 | })), 13 | state('active', style({ 14 | height: '200px' 15 | })), 16 | transition('inactive => active', animate('300ms ease-in')), 17 | transition('active => inactive', animate('300ms ease-out')) 18 | ]) 19 | ], 20 | host: { 21 | '(document:click)': 'documentClick($event)', 22 | } 23 | }) 24 | export class DropdownComponent implements OnInit { 25 | dropdownMenu = { 26 | state: 'inactive' 27 | }; 28 | @Input() Label: string; 29 | @Input() DropdownMenu: Array; 30 | @Output() SelectItem = new EventEmitter(); 31 | constructor() { } 32 | 33 | ngOnInit() { 34 | } 35 | 36 | documentClick(e) { 37 | this.dropdownMenu.state = 'inactive'; 38 | e.stopPropagation(); 39 | } 40 | 41 | toggleDropdownMenu(e) { 42 | this.dropdownMenu.state === 'active' ? this.dropdownMenu.state = 'inactive' : this.dropdownMenu.state = 'active'; 43 | e.stopPropagation(); 44 | } 45 | selectItem(data) { 46 | this.dropdownMenu.state = 'inactive'; 47 | this.SelectItem.emit(data); 48 | } 49 | } 50 | 51 | export interface dropdownItem { 52 | name: object; 53 | id: number; 54 | } 55 | -------------------------------------------------------------------------------- /src/app/directives/markdown-editor/markdown-editor.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { MarkdownEditorDirective } from './markdown-editor.directive'; 2 | 3 | describe('MarkdownEditorDirective', () => { 4 | it('should create an instance', () => { 5 | const directive = new MarkdownEditorDirective(); 6 | expect(directive).toBeTruthy(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /src/app/directives/markdown-editor/markdown-editor.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, Input, Output, EventEmitter } from '@angular/core'; 2 | import SimpleMDE from 'simplemde'; 3 | import marked from 'marked'; 4 | import highlight from 'highlight.js'; 5 | 6 | @Directive({ 7 | selector: '[myMarkdownEditor]' 8 | }) 9 | export class MarkdownEditorDirective { 10 | @Input() inputMarkdownEditorValue: String = ''; 11 | // 指令往外部输出编辑器的值 12 | @Output() MarkdownEditorValue = new EventEmitter(); 13 | 14 | constructor(private el: ElementRef) { 15 | setTimeout(() => { 16 | const simpleMDE = new SimpleMDE({ 17 | element: el.nativeElement.querySelector('textarea'), 18 | autofocus: false, 19 | autosave: true, 20 | previewRender: function(plainText) { 21 | return marked(plainText, { 22 | renderer: new marked.Renderer(), 23 | gfm: true, 24 | pedantic: false, 25 | sanitize: false, 26 | tables: true, 27 | breaks: true, 28 | smartLists: true, 29 | smartypants: true, 30 | highlight: function (code) { 31 | return highlight.highlightAuto(code).value; 32 | } 33 | }); 34 | }, 35 | toolbar: ['bold', 'italic', 'strikethrough', 'heading', 'code', 'quote', 'unordered-list', 'ordered-list', 'clean-block', 'link', 'image', 'table', 'horizontal-rule', 'preview', { 36 | name: 'side-by-side', 37 | action: function(editor){ 38 | const cm = editor.codemirror; 39 | const wrapper = cm.getWrapperElement(); 40 | const preview = wrapper.nextSibling; 41 | const toolbarButton = editor.toolbarElements['side-by-side']; 42 | let useSideBySideListener = false; 43 | if (/editor-preview-active-side/.test(preview.className)) { 44 | preview.className = preview.className.replace( 45 | /\s*editor-preview-active-side\s*/g, '' 46 | ); 47 | toolbarButton.className = toolbarButton.className.replace(/\s*active\s*/g, ''); 48 | wrapper.className = wrapper.className.replace(/\s*CodeMirror-sided\s*/g, ' '); 49 | } else { 50 | // When the preview button is clicked for the first time, 51 | // give some time for the transition from editor.css to fire and the view to slide from right to left, 52 | // instead of just appearing. 53 | setTimeout(function() { 54 | if (!cm.getOption('fullScreen')) { 55 | simpleMDE.toggleFullScreen(editor); 56 | } 57 | 58 | const fullscreenDom = document.querySelector('.CodeMirror-fullscreen'); 59 | if (!fullscreenDom) { 60 | return; 61 | } 62 | fullscreenDom.style.height = window.innerHeight - 70 + 'px'; 63 | fullscreenDom.style.maxHeight = window.innerHeight - 70 + 'px'; 64 | preview.className += ' editor-preview-active-side'; 65 | }, 1); 66 | toolbarButton.className += ' active'; 67 | wrapper.className += ' CodeMirror-sided'; 68 | useSideBySideListener = true; 69 | } 70 | 71 | // Hide normal preview if active 72 | const previewNormal = wrapper.lastChild; 73 | if (/editor-preview-active/.test(previewNormal.className)) { 74 | previewNormal.className = previewNormal.className.replace( 75 | /\s*editor-preview-active\s*/g, '' 76 | ); 77 | const toolbar = editor.toolbarElements.preview; 78 | const toolbar_div = wrapper.previousSibling; 79 | toolbar.className = toolbar.className.replace(/\s*active\s*/g, ''); 80 | toolbar_div.className = toolbar_div.className.replace(/\s*disabled-for-preview*/g, ''); 81 | } 82 | 83 | const sideBySideRenderingFunction = function() { 84 | preview.innerHTML = editor.options.previewRender(editor.value(), preview); 85 | }; 86 | 87 | if (!cm.sideBySideRenderingFunction) { 88 | cm.sideBySideRenderingFunction = sideBySideRenderingFunction; 89 | } 90 | 91 | if (useSideBySideListener) { 92 | preview.innerHTML = editor.options.previewRender(editor.value(), preview); 93 | cm.on('update', cm.sideBySideRenderingFunction); 94 | } else { 95 | cm.off('update', cm.sideBySideRenderingFunction); 96 | } 97 | 98 | // Refresh to fix selection being off (#309) 99 | cm.refresh(); 100 | }, 101 | className: 'fa fa-columns no-disable no-mobile', 102 | title: 'sideBySide' 103 | }, { 104 | name: 'fullscreen', 105 | action: function(editor){ 106 | let saved_overflow = ''; 107 | // Set fullscreen 108 | const cm = editor.codemirror; 109 | cm.setOption('fullScreen', !cm.getOption('fullScreen')); 110 | 111 | // Prevent scrolling on body during fullscreen active 112 | if (cm.getOption('fullScreen')) { 113 | saved_overflow = document.body.style.overflow; 114 | document.body.style.overflow = 'hidden'; 115 | } else { 116 | document.body.style.overflow = saved_overflow; 117 | } 118 | 119 | // Update toolbar class 120 | const wrap = cm.getWrapperElement(); 121 | 122 | if (!/fullscreen/.test(wrap.previousSibling.className)) { 123 | wrap.previousSibling.className += ' fullscreen'; 124 | } else { 125 | wrap.previousSibling.className = wrap.previousSibling.className.replace(/\s*fullscreen\b/, ''); 126 | } 127 | 128 | // Update toolbar button 129 | const toolbarButton = editor.toolbarElements.fullscreen; 130 | 131 | if (!/active/.test(toolbarButton.className)) { 132 | toolbarButton.className += 'active'; 133 | } else { 134 | toolbarButton.className = toolbarButton.className.replace(/\s*active\s*/g, ''); 135 | } 136 | 137 | // Hide side by side if needed 138 | const sidebyside = cm.getWrapperElement().nextSibling; 139 | if (/editor-preview-active-side/.test(sidebyside.className)) { 140 | simpleMDE.toggleSideBySide(editor); 141 | } 142 | 143 | const fullscreenDom = document.querySelector('.CodeMirror-fullscreen'); 144 | if (!fullscreenDom) { 145 | return; 146 | } 147 | fullscreenDom.style.height = window.innerHeight - 70 + 'px'; 148 | fullscreenDom.style.maxHeight = window.innerHeight - 70 + 'px'; 149 | // let toolbar = document.querySelector('.editor-toolbar') 150 | // let input = document.createElement('input') 151 | // input.value = '这是标题' 152 | // toolbar.appendChild(input) 153 | }, 154 | className: 'fa fa-arrows-alt no-disable no-mobile', 155 | title: 'Fullscreen', 156 | }], 157 | styleSelectedText: false 158 | }); 159 | simpleMDE.codemirror.on('change', () => { 160 | this.MarkdownEditorValue.emit(simpleMDE.value()); 161 | }); 162 | simpleMDE.value(this.inputMarkdownEditorValue); 163 | // simpleMDE.togglePreview() 164 | el.nativeElement.querySelector('.CodeMirror').style.height = window.innerHeight - 220 + 'px'; 165 | el.nativeElement.querySelector('.CodeMirror').style.maxHeight = window.innerHeight - 220 + 'px'; 166 | el.nativeElement.querySelector('.CodeMirror').style.overflow = 'auto'; 167 | }); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/app/directives/markdown-editor/simplemde.min.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * simplemde v1.11.2 3 | * Copyright Next Step Webs, Inc. 4 | * @link https://github.com/NextStepWebs/simplemde-markdown-editor 5 | * @license MIT 6 | */ 7 | .CodeMirror { 8 | color: #000; 9 | } 10 | 11 | .CodeMirror-lines { 12 | padding: 4px 0; 13 | } 14 | 15 | .CodeMirror pre { 16 | padding: 0 4px; 17 | } 18 | 19 | .CodeMirror-gutter-filler, .CodeMirror-scrollbar-filler { 20 | background-color: #fff; 21 | } 22 | 23 | .CodeMirror-gutters { 24 | border-right: 1px solid #ddd; 25 | background-color: #f7f7f7; 26 | white-space: nowrap; 27 | } 28 | 29 | .CodeMirror-linenumber { 30 | padding: 0 3px 0 5px; 31 | min-width: 20px; 32 | text-align: right; 33 | color: #999; 34 | white-space: nowrap; 35 | } 36 | 37 | .CodeMirror-guttermarker { 38 | color: #000; 39 | } 40 | 41 | .CodeMirror-guttermarker-subtle { 42 | color: #999; 43 | } 44 | 45 | .CodeMirror-cursor { 46 | border-left: 1px solid #000; 47 | border-right: none; 48 | width: 0; 49 | } 50 | 51 | .CodeMirror div.CodeMirror-secondarycursor { 52 | border-left: 1px solid silver; 53 | } 54 | 55 | .cm-fat-cursor .CodeMirror-cursor { 56 | width: auto; 57 | border: 0 !important; 58 | background: #7e7; 59 | } 60 | 61 | .cm-fat-cursor div.CodeMirror-cursors { 62 | z-index: 1; 63 | } 64 | 65 | .cm-animate-fat-cursor { 66 | width: auto; 67 | border: 0; 68 | -webkit-animation: blink 1.06s steps(1) infinite; 69 | -moz-animation: blink 1.06s steps(1) infinite; 70 | animation: blink 1.06s steps(1) infinite; 71 | background-color: #7e7; 72 | } 73 | 74 | @-moz-keyframes blink { 75 | 50% { 76 | background-color: transparent; 77 | } 78 | } 79 | 80 | @-webkit-keyframes blink { 81 | 50% { 82 | background-color: transparent; 83 | } 84 | } 85 | 86 | @keyframes blink { 87 | 50% { 88 | background-color: transparent; 89 | } 90 | } 91 | 92 | .cm-tab { 93 | display: inline-block; 94 | text-decoration: inherit; 95 | } 96 | 97 | .CodeMirror-ruler { 98 | border-left: 1px solid #ccc; 99 | position: absolute; 100 | } 101 | 102 | .cm-s-default .cm-header { 103 | color: #00f; 104 | } 105 | 106 | .cm-s-default .cm-quote { 107 | color: #090; 108 | } 109 | 110 | .cm-negative { 111 | color: #d44; 112 | } 113 | 114 | .cm-positive { 115 | color: #292; 116 | } 117 | 118 | .cm-header, .cm-strong { 119 | font-weight: 700; 120 | } 121 | 122 | .cm-em { 123 | font-style: italic; 124 | } 125 | 126 | .cm-link { 127 | text-decoration: underline; 128 | } 129 | 130 | .cm-strikethrough { 131 | text-decoration: line-through; 132 | } 133 | 134 | .cm-s-default .cm-keyword { 135 | color: #708; 136 | } 137 | 138 | .cm-s-default .cm-atom { 139 | color: #219; 140 | } 141 | 142 | .cm-s-default .cm-number { 143 | color: #164; 144 | } 145 | 146 | .cm-s-default .cm-def { 147 | color: #00f; 148 | } 149 | 150 | .cm-s-default .cm-variable-2 { 151 | color: #05a; 152 | } 153 | 154 | .cm-s-default .cm-variable-3 { 155 | color: #085; 156 | } 157 | 158 | .cm-s-default .cm-comment { 159 | color: #a50; 160 | } 161 | 162 | .cm-s-default .cm-string { 163 | color: #a11; 164 | } 165 | 166 | .cm-s-default .cm-string-2 { 167 | color: #f50; 168 | } 169 | 170 | .cm-s-default .cm-meta, .cm-s-default .cm-qualifier { 171 | color: #555; 172 | } 173 | 174 | .cm-s-default .cm-builtin { 175 | color: #30a; 176 | } 177 | 178 | .cm-s-default .cm-bracket { 179 | color: #997; 180 | } 181 | 182 | .cm-s-default .cm-tag { 183 | color: #170; 184 | } 185 | 186 | .cm-s-default .cm-attribute { 187 | color: #00c; 188 | } 189 | 190 | .cm-s-default .cm-hr { 191 | color: #999; 192 | } 193 | 194 | .cm-s-default .cm-link { 195 | color: #00c; 196 | } 197 | 198 | .cm-invalidchar, .cm-s-default .cm-error { 199 | color: red; 200 | } 201 | 202 | .CodeMirror-composing { 203 | border-bottom: 2px solid; 204 | } 205 | 206 | div.CodeMirror span.CodeMirror-matchingbracket { 207 | color: #0f0; 208 | } 209 | 210 | div.CodeMirror span.CodeMirror-nonmatchingbracket { 211 | color: #f22; 212 | } 213 | 214 | .CodeMirror-matchingtag { 215 | background: rgba(255, 150, 0, 0.3); 216 | } 217 | 218 | .CodeMirror-activeline-background { 219 | background: #e8f2ff; 220 | } 221 | 222 | .CodeMirror { 223 | position: relative; 224 | overflow: hidden; 225 | background: #fff; 226 | } 227 | 228 | .CodeMirror-scroll { 229 | overflow: scroll !important; 230 | margin-bottom: -30px; 231 | margin-right: -30px; 232 | padding-bottom: 30px; 233 | height: 100%; 234 | outline: 0; 235 | position: relative; 236 | } 237 | 238 | .CodeMirror-sizer { 239 | position: relative; 240 | border-right: 30px solid transparent; 241 | } 242 | 243 | .CodeMirror-gutter-filler, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-vscrollbar { 244 | position: absolute; 245 | z-index: 6; 246 | display: none; 247 | } 248 | 249 | .CodeMirror-vscrollbar { 250 | right: 0; 251 | top: 0; 252 | overflow-x: hidden; 253 | overflow-y: scroll; 254 | } 255 | 256 | .CodeMirror-hscrollbar { 257 | bottom: 0; 258 | left: 0; 259 | overflow-y: hidden; 260 | overflow-x: scroll; 261 | } 262 | 263 | .CodeMirror-scrollbar-filler { 264 | right: 0; 265 | bottom: 0; 266 | } 267 | 268 | .CodeMirror-gutter-filler { 269 | left: 0; 270 | bottom: 0; 271 | } 272 | 273 | .CodeMirror-gutters { 274 | position: absolute; 275 | left: 0; 276 | top: 0; 277 | min-height: 100%; 278 | z-index: 3; 279 | } 280 | 281 | .CodeMirror-gutter { 282 | white-space: normal; 283 | height: 100%; 284 | display: inline-block; 285 | vertical-align: top; 286 | margin-bottom: -30px; 287 | } 288 | 289 | .CodeMirror-gutter-wrapper { 290 | position: absolute; 291 | z-index: 4; 292 | background: 0 0 !important; 293 | border: none !important; 294 | -webkit-user-select: none; 295 | -moz-user-select: none; 296 | user-select: none; 297 | } 298 | 299 | .CodeMirror-gutter-background { 300 | position: absolute; 301 | top: 0; 302 | bottom: 0; 303 | z-index: 4; 304 | } 305 | 306 | .CodeMirror-gutter-elt { 307 | position: absolute; 308 | cursor: default; 309 | z-index: 4; 310 | } 311 | 312 | .CodeMirror-lines { 313 | cursor: text; 314 | min-height: 1px; 315 | } 316 | 317 | .CodeMirror pre { 318 | -moz-border-radius: 0; 319 | -webkit-border-radius: 0; 320 | border-radius: 0; 321 | border-width: 0; 322 | background: 0 0; 323 | font-family: inherit; 324 | font-size: inherit; 325 | margin: 0; 326 | white-space: pre; 327 | word-wrap: normal; 328 | line-height: inherit; 329 | color: inherit; 330 | z-index: 2; 331 | position: relative; 332 | overflow: visible; 333 | -webkit-tap-highlight-color: transparent; 334 | -webkit-font-variant-ligatures: none; 335 | font-variant-ligatures: none; 336 | } 337 | 338 | .CodeMirror-wrap pre { 339 | word-wrap: break-word; 340 | white-space: pre-wrap; 341 | word-break: normal; 342 | 343 | /* margin: 15px 0; */ 344 | } 345 | 346 | .CodeMirror-linebackground { 347 | position: absolute; 348 | left: 0; 349 | right: 0; 350 | top: 0; 351 | bottom: 0; 352 | z-index: 0; 353 | } 354 | 355 | .CodeMirror-linewidget { 356 | position: relative; 357 | z-index: 2; 358 | overflow: auto; 359 | } 360 | 361 | .CodeMirror-code { 362 | outline: 0; 363 | } 364 | 365 | .CodeMirror-gutter, .CodeMirror-gutters, .CodeMirror-linenumber, .CodeMirror-scroll, .CodeMirror-sizer { 366 | -moz-box-sizing: content-box; 367 | box-sizing: content-box; 368 | } 369 | 370 | .CodeMirror-measure { 371 | position: absolute; 372 | width: 100%; 373 | height: 0; 374 | overflow: hidden; 375 | visibility: hidden; 376 | } 377 | 378 | .CodeMirror-cursor { 379 | position: absolute; 380 | } 381 | 382 | .CodeMirror-measure pre { 383 | position: static; 384 | } 385 | 386 | div.CodeMirror-cursors { 387 | visibility: hidden; 388 | position: relative; 389 | z-index: 3; 390 | } 391 | 392 | .CodeMirror-focused div.CodeMirror-cursors, div.CodeMirror-dragcursors { 393 | visibility: visible; 394 | } 395 | 396 | .CodeMirror-selected { 397 | background: #d9d9d9; 398 | } 399 | 400 | .CodeMirror-focused .CodeMirror-selected, .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { 401 | background: #d7d4f0; 402 | } 403 | 404 | .CodeMirror-crosshair { 405 | cursor: crosshair; 406 | } 407 | 408 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { 409 | background: #d7d4f0; 410 | } 411 | 412 | .cm-searching { 413 | background: #ffa; 414 | background: rgba(255, 255, 0, 0.4); 415 | } 416 | 417 | .cm-force-border { 418 | padding-right: .1px; 419 | } 420 | 421 | @media print { 422 | .CodeMirror div.CodeMirror-cursors { 423 | visibility: hidden; 424 | } 425 | } 426 | 427 | .cm-tab-wrap-hack:after { 428 | content: ''; 429 | } 430 | 431 | span.CodeMirror-selectedtext { 432 | background: 0 0; 433 | } 434 | 435 | .CodeMirror { 436 | height: auto; 437 | min-height: 300px; 438 | border-top: 1px solid #fbfbfb; 439 | border-bottom-left-radius: 4px; 440 | border-bottom-right-radius: 4px; 441 | padding: 10px; 442 | font: inherit; 443 | z-index: 1; 444 | background-color: #f7f7f7; 445 | &::-webkit-scrollbar{ 446 | width: 0; 447 | height: 0; 448 | } 449 | } 450 | 451 | .CodeMirror-scroll { 452 | min-height: 300px; 453 | } 454 | 455 | .CodeMirror-fullscreen { 456 | background: #fff; 457 | position: fixed !important; 458 | top: 50px; 459 | left: 0; 460 | right: 0; 461 | bottom: 0; 462 | height: auto; 463 | z-index: 9; 464 | border-top: 1px solid #ccc; 465 | } 466 | 467 | .CodeMirror-fullscreen ::-webkit-scrollbar { 468 | width: 0; 469 | height: 0; 470 | } 471 | 472 | .CodeMirror-sided { 473 | width: 49.5vw !important; 474 | 475 | /* overflow: hidden; */ 476 | } 477 | 478 | .CodeMirror-sided ::-webkit-scrollbar { 479 | width: 0; 480 | height: 0; 481 | } 482 | 483 | .editor-toolbar { 484 | position: relative; 485 | opacity: .6; 486 | -webkit-user-select: none; 487 | -moz-user-select: none; 488 | -ms-user-select: none; 489 | -o-user-select: none; 490 | user-select: none; 491 | padding: 0 10px; 492 | 493 | /* border-top: 1px solid #bbb; */ 494 | /* border-left: 1px solid #bbb; */ 495 | /* border-right: 1px solid #bbb; */ 496 | /* border-top-left-radius: 4px; */ 497 | /* border-top-right-radius: 4px; */ 498 | } 499 | 500 | .editor-toolbar:after, .editor-toolbar:before { 501 | display: block; 502 | content: ' '; 503 | height: 1px; 504 | } 505 | 506 | .editor-toolbar:before { 507 | margin-bottom: 8px; 508 | } 509 | 510 | .editor-toolbar:after { 511 | margin-top: 8px; 512 | } 513 | 514 | .editor-toolbar:hover, .editor-wrapper input.title:focus, .editor-wrapper input.title:hover { 515 | opacity: .8; 516 | } 517 | 518 | .editor-toolbar.fullscreen { 519 | width: 100%; 520 | height: 50px; 521 | overflow-x: auto; 522 | overflow-y: hidden; 523 | white-space: nowrap; 524 | padding-top: 10px; 525 | padding-bottom: 10px; 526 | box-sizing: border-box; 527 | background: #fff; 528 | border: 0; 529 | position: fixed; 530 | top: 0; 531 | left: 0; 532 | opacity: 1; 533 | z-index: 9; 534 | } 535 | 536 | .editor-toolbar.fullscreen::before { 537 | width: 20px; 538 | height: 50px; 539 | background: -moz-linear-gradient(left, rgba(255, 255, 255, 1) 0, rgba(255, 255, 255, 0) 100%); 540 | background: -webkit-gradient(linear, left top, right top, color-stop(0, rgba(255, 255, 255, 1)), color-stop(100%, rgba(255, 255, 255, 0))); 541 | background: -webkit-linear-gradient(left, rgba(255, 255, 255, 1) 0, rgba(255, 255, 255, 0) 100%); 542 | background: -o-linear-gradient(left, rgba(255, 255, 255, 1) 0, rgba(255, 255, 255, 0) 100%); 543 | background: -ms-linear-gradient(left, rgba(255, 255, 255, 1) 0, rgba(255, 255, 255, 0) 100%); 544 | background: linear-gradient(to right, rgba(255, 255, 255, 1) 0, rgba(255, 255, 255, 0) 100%); 545 | position: fixed; 546 | top: 0; 547 | left: 0; 548 | margin: 0; 549 | padding: 0; 550 | } 551 | 552 | .editor-toolbar.fullscreen::after { 553 | width: 20px; 554 | height: 50px; 555 | background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 1) 100%); 556 | background: -webkit-gradient(linear, left top, right top, color-stop(0, rgba(255, 255, 255, 0)), color-stop(100%, rgba(255, 255, 255, 1))); 557 | background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 1) 100%); 558 | background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 1) 100%); 559 | background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 1) 100%); 560 | background: linear-gradient(to right, rgba(255, 255, 255, 0) 0, rgba(255, 255, 255, 1) 100%); 561 | position: fixed; 562 | top: 0; 563 | right: 0; 564 | margin: 0; 565 | padding: 0; 566 | } 567 | 568 | .editor-toolbar a { 569 | display: inline-block; 570 | text-align: center; 571 | text-decoration: none !important; 572 | color: #2c3e50 !important; 573 | width: 30px; 574 | height: 30px; 575 | margin: 0; 576 | border: 1px solid transparent; 577 | border-radius: 3px; 578 | cursor: pointer; 579 | } 580 | 581 | .editor-toolbar a.active, .editor-toolbar a:hover { 582 | background: #fcfcfc; 583 | border-color: #95a5a6; 584 | } 585 | 586 | .editor-toolbar a:before { 587 | line-height: 30px; 588 | } 589 | 590 | .editor-toolbar i.separator { 591 | display: inline-block; 592 | width: 0; 593 | border-left: 1px solid #d9d9d9; 594 | border-right: 1px solid #fff; 595 | color: transparent; 596 | text-indent: -10px; 597 | margin: 0 6px; 598 | } 599 | 600 | .editor-toolbar a.fa-header-x:after { 601 | font-family: Arial,"Helvetica Neue",Helvetica,sans-serif; 602 | font-size: 65%; 603 | vertical-align: text-bottom; 604 | position: relative; 605 | top: 2px; 606 | } 607 | 608 | .editor-toolbar a.fa-header-1:after { 609 | content: "1"; 610 | } 611 | 612 | .editor-toolbar a.fa-header-2:after { 613 | content: "2"; 614 | } 615 | 616 | .editor-toolbar a.fa-header-3:after { 617 | content: "3"; 618 | } 619 | 620 | .editor-toolbar a.fa-header-bigger:after { 621 | content: "▲"; 622 | } 623 | 624 | .editor-toolbar a.fa-header-smaller:after { 625 | content: "▼"; 626 | } 627 | 628 | .editor-toolbar.disabled-for-preview a:not(.no-disable) { 629 | pointer-events: none; 630 | background: #fff; 631 | border-color: transparent; 632 | text-shadow: inherit; 633 | } 634 | 635 | @media only screen and (max-width: 700px) { 636 | .editor-toolbar a.no-mobile { 637 | display: none; 638 | } 639 | } 640 | 641 | .editor-statusbar { 642 | display: none; 643 | padding: 8px 10px; 644 | font-size: 12px; 645 | color: #959694; 646 | text-align: right; 647 | } 648 | 649 | .editor-statusbar span { 650 | display: inline-block; 651 | min-width: 4em; 652 | margin-left: 1em; 653 | } 654 | 655 | .editor-preview, .editor-preview-side { 656 | padding: 10px; 657 | background: #fafafa; 658 | overflow: auto; 659 | display: none; 660 | box-sizing: border-box; 661 | } 662 | 663 | .editor-preview, .editor-preview-side{ 664 | &::-webkit-scrollbar{ 665 | width: 0; 666 | } 667 | img { 668 | max-width: 100%; 669 | } 670 | 671 | th, td { 672 | border: 1px solid #E6E6E6; 673 | padding: 5px 8px; 674 | } 675 | 676 | table { 677 | border-collapse: collapse; 678 | } 679 | 680 | blockquote { 681 | margin: 1em 0; 682 | border-left: 4px solid #3db8c1; 683 | padding: 10px; 684 | color: #666; 685 | background-color: #eeeeee; 686 | p { 687 | line-height: 2; 688 | display: block; 689 | margin-bottom: 0px; 690 | 691 | a { 692 | color: #3db8c1; 693 | text-decoration: none; 694 | } 695 | } 696 | ul { 697 | list-style-type: none; 698 | padding: 0; 699 | margin: 0; 700 | } 701 | } 702 | 703 | h1 { 704 | margin: 1.3rem 0; 705 | line-height: 1.2; 706 | font-size: 2em; 707 | } 708 | 709 | h2 { 710 | border-bottom: 1px solid #eee; 711 | padding-bottom: 10px; 712 | } 713 | 714 | h3 { 715 | margin: 20px 0; 716 | } 717 | 718 | p { 719 | line-height: 2; 720 | display: block; 721 | margin-bottom: 20px; 722 | 723 | a { 724 | color: #3db8c1; 725 | text-decoration: none; 726 | } 727 | 728 | video{ 729 | width: 100%; 730 | } 731 | } 732 | 733 | ul { 734 | padding-left: 2.7rem; 735 | display: block; 736 | list-style-type: disc; 737 | 738 | li { 739 | margin-bottom: .6rem; 740 | list-style: inherit; 741 | } 742 | } 743 | 744 | code { 745 | font-family: Menlo,Monaco,Consolas,Courier New,monospace; 746 | font-size: 1rem; 747 | padding: 2px 5px; 748 | word-break: break-word; 749 | color: #49a9ee; 750 | background-color: #f8f8f8; 751 | border-radius: 2px; 752 | overflow-x: scroll; 753 | font-size: 12px; 754 | margin: 0 4px; 755 | } 756 | 757 | pre { 758 | white-space: pre; 759 | margin: 1em 0px; 760 | padding: 5px; 761 | width: 98%; 762 | max-width: 100%; 763 | overflow: scroll; 764 | border-radius: 3px; 765 | background-color: #eeeeee; 766 | 767 | code { 768 | color: #333 !important; 769 | background-color: #eeeeee; 770 | 771 | .hljs-function { 772 | color: #333; 773 | } 774 | 775 | .hljs-keyword { 776 | color: #333; 777 | font-weight: 700; 778 | } 779 | 780 | .hljs-title { 781 | color: #900; 782 | font-weight: 700; 783 | } 784 | 785 | .hljs-params { 786 | color: #333; 787 | } 788 | 789 | .hljs-comment { 790 | color: #998; 791 | } 792 | 793 | .hljs-string { 794 | color: #d14; 795 | } 796 | 797 | .hljs-number { 798 | color: teal; 799 | } 800 | 801 | .hljs-literal { 802 | color: teal; 803 | } 804 | } 805 | } 806 | } 807 | 808 | .editor-statusbar .lines:before { 809 | content: 'lines: '; 810 | } 811 | 812 | .editor-statusbar .words:before { 813 | content: 'words: '; 814 | } 815 | 816 | .editor-statusbar .characters:before { 817 | content: 'characters: '; 818 | } 819 | 820 | .editor-preview { 821 | position: absolute; 822 | width: 100%; 823 | height: 100%; 824 | top: 0; 825 | left: 0; 826 | z-index: 7; 827 | } 828 | 829 | .editor-preview-side { 830 | position: fixed; 831 | bottom: 0; 832 | width: 50vw; 833 | top: 50px; 834 | right: 0; 835 | z-index: 9; 836 | border: 1px solid #ddd; 837 | } 838 | 839 | .editor-preview-active, .editor-preview-active-side { 840 | display: block; 841 | } 842 | 843 | .editor-preview-side > p, .editor-preview > p { 844 | line-height: 2; 845 | display: block; 846 | margin-bottom: 0px; 847 | 848 | a { 849 | color: #3db8c1; 850 | text-decoration: none; 851 | } 852 | } 853 | 854 | .editor-preview pre, .editor-preview-side pre { 855 | background: #eee; 856 | margin-bottom: 10px; 857 | } 858 | 859 | .editor-preview table td, .editor-preview table th, .editor-preview-side table td, .editor-preview-side table th { 860 | border: 1px solid #ddd; 861 | padding: 5px; 862 | } 863 | 864 | .CodeMirror .CodeMirror-code .cm-tag { 865 | color: #63a35c; 866 | } 867 | 868 | .CodeMirror .CodeMirror-code .cm-attribute { 869 | color: #795da3; 870 | } 871 | 872 | .CodeMirror .CodeMirror-code .cm-string { 873 | color: #183691; 874 | } 875 | 876 | .CodeMirror .CodeMirror-selected { 877 | background: #d9d9d9; 878 | } 879 | 880 | .CodeMirror .CodeMirror-code .cm-header-1 { 881 | font-size: 200%; 882 | line-height: 200%; 883 | } 884 | 885 | .CodeMirror .CodeMirror-code .cm-header-2 { 886 | font-size: 160%; 887 | line-height: 160%; 888 | } 889 | 890 | .CodeMirror .CodeMirror-code .cm-header-3 { 891 | font-size: 125%; 892 | line-height: 125%; 893 | } 894 | 895 | .CodeMirror .CodeMirror-code .cm-header-4 { 896 | font-size: 110%; 897 | line-height: 110%; 898 | } 899 | 900 | .CodeMirror .CodeMirror-code .cm-comment { 901 | /* background: rgba(0, 0, 0, 0.05); */ 902 | border-radius: 2px; 903 | color: #3db8c1; 904 | } 905 | 906 | .CodeMirror .CodeMirror-code .cm-link { 907 | color: #7f8c8d; 908 | } 909 | 910 | .CodeMirror .CodeMirror-code .cm-url { 911 | color: #aab2b3; 912 | } 913 | 914 | .CodeMirror .CodeMirror-code .cm-strikethrough { 915 | text-decoration: line-through; 916 | } 917 | 918 | .CodeMirror .CodeMirror-placeholder { 919 | opacity: .5; 920 | } 921 | 922 | .CodeMirror .cm-spell-error:not(.cm-url):not(.cm-comment):not(.cm-tag):not(.cm-word) { 923 | /* background: rgba(255, 0, 0, 0.15); */ 924 | } 925 | -------------------------------------------------------------------------------- /src/app/interceptor/global-response-interceptor.ts: -------------------------------------------------------------------------------- 1 | import { MsgService } from './../services/msg/msg.service'; 2 | import { Router, Routes } from '@angular/router'; 3 | import { Injectable } from '@angular/core'; 4 | import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http'; 5 | import { Observable } from 'rxjs/Rx'; 6 | import { HttpResponse } from '@angular/common/http'; 7 | 8 | @Injectable() 9 | export class GlobalResponseInterceptor implements HttpInterceptor { 10 | 11 | constructor( 12 | private router: Router, 13 | private msg: MsgService 14 | ) { 15 | } 16 | 17 | intercept(req: HttpRequest < any > , next: HttpHandler): Observable < HttpEvent < any >> { 18 | return next.handle(req).map(event => { 19 | if (event instanceof HttpResponse) { 20 | if (event.body.code === 401) { 21 | this.msg.info('登录已过期!'); 22 | setTimeout(() => { 23 | this.router.navigate(['/index']); 24 | }, 2000); 25 | } 26 | } 27 | return event; 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/app/page/add-link-note/add-link-note.component.html: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 |
12 | 13 |
14 |
15 |
16 |
17 | 18 |
19 |
    20 |
  • 21 |
    {{item.name}}
    22 | 23 |
  • 24 |
25 |
26 |
27 | 保存 28 | 取消 29 |
30 |
31 |
32 |
-------------------------------------------------------------------------------- /src/app/page/add-link-note/add-link-note.component.scss: -------------------------------------------------------------------------------- 1 | @import '../add-note/add-note.component.scss'; 2 | .add-link-note{ 3 | height: 100vh; 4 | padding: 1vh 8vh 30vh 8vh; 5 | .input-wrap{ 6 | height:52px; 7 | line-height:52px; 8 | margin-top: 19vh; 9 | .search-input{ 10 | border-bottom: 1px solid #ccc; 11 | background-color: transparent; 12 | margin-bottom: 3vh; 13 | border: none; 14 | color: #ccc; 15 | height: 52px; 16 | width: 100%; 17 | overflow-x: scroll; 18 | font-size: 40px; 19 | outline: none; 20 | } 21 | }; 22 | .note-name{ 23 | border-bottom: 1px solid #ececec; 24 | padding-bottom: 3vh; 25 | font-size: 13px; 26 | margin-top: 3rem; 27 | } 28 | } 29 | 30 | .add-note-toolbar{ 31 | padding: 10px 0; 32 | border-bottom: 1px solid #ececec; 33 | } 34 | 35 | .preview{ 36 | font-size: 14px !important; 37 | padding: 10px 30px; 38 | background-color: #fbfbfb; 39 | &::-webkit-scrollbar{ 40 | width: 0; 41 | } 42 | pre{ 43 | background-color: #eee; 44 | padding: 5px; 45 | max-width: 100%; 46 | overflow: auto; 47 | } 48 | img{ 49 | max-width: 100%; 50 | } 51 | th, td{ 52 | border: 1px solid #E6E6E6; 53 | padding: 5px 8px; 54 | } 55 | table{ 56 | border-collapse: collapse; 57 | } 58 | blockquote{ 59 | margin: 1em 0; 60 | border-left: 4px solid #ddd; 61 | padding: 0 1.3rem; 62 | color: #666; 63 | } 64 | h1{ 65 | margin: 1.3rem 0; 66 | line-height: 1.2; 67 | font-size: 2em; 68 | } 69 | h2{ 70 | border-bottom: 1px solid #eee; 71 | padding-bottom: 10px; 72 | } 73 | h3{ 74 | margin: 20px 0; 75 | } 76 | p{ 77 | line-height: 2; 78 | display: block; 79 | margin-bottom: 20px; 80 | a{ 81 | color: #3dbd7d; 82 | text-decoration: none; 83 | } 84 | video{ 85 | width: 100%; 86 | } 87 | } 88 | ul{ 89 | padding-left: 2.7rem; 90 | display: block; 91 | list-style-type: disc; 92 | li{ 93 | margin-bottom: .6rem; 94 | list-style: inherit; 95 | } 96 | } 97 | code{ 98 | font-family: Menlo,Monaco,Consolas,Courier New,monospace; 99 | font-size: 1rem; 100 | padding: 2px 5px; 101 | word-break: break-word; 102 | color: #49a9ee; 103 | background-color: #f8f8f8; 104 | border-radius: 2px; 105 | overflow-x: scroll; 106 | font-size: 12px; 107 | margin: 0 4px; 108 | } 109 | pre{ 110 | white-space: pre; 111 | margin: 1em 0px; 112 | padding: 5px; 113 | width: 100%; 114 | max-width: 100%; 115 | overflow: scroll; 116 | border-radius: 3px; 117 | code{ 118 | color: #333 !important; 119 | background-color: #eeeeee; 120 | .hljs-function{ 121 | color: #333; 122 | } 123 | .hljs-keyword{ 124 | color: #333; 125 | font-weight: 700; 126 | } 127 | .hljs-title{ 128 | color: #900; 129 | font-weight: 700; 130 | } 131 | .hljs-params{ 132 | color: #333; 133 | } 134 | .hljs-comment{ 135 | color: #998; 136 | } 137 | .hljs-string{ 138 | color: #d14; 139 | } 140 | .hljs-number{ 141 | color: teal; 142 | } 143 | .hljs-literal{ 144 | color: teal; 145 | } 146 | } 147 | } 148 | } -------------------------------------------------------------------------------- /src/app/page/add-link-note/add-link-note.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AddLinkNoteComponent } from './add-link-note.component'; 4 | 5 | describe('AddLinkNoteComponent', () => { 6 | let component: AddLinkNoteComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ AddLinkNoteComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AddLinkNoteComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/page/add-link-note/add-link-note.component.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Subscription } from 'rxjs/Subscription'; 3 | import { Router } from '@angular/router'; 4 | import { MsgService } from './../../services/msg/msg.service'; 5 | import { NoteService } from './../../services/note/note.service'; 6 | import { style } from '@angular/animations'; 7 | import { TagService } from './../../services/tag/tag.service'; 8 | import { LoadingBarService } from './../../services/loading-bar/loading-bar.service'; 9 | import { Component, OnInit, ViewEncapsulation, Directive, ElementRef, OnDestroy } from '@angular/core'; 10 | import 'rxjs/add/operator/map'; 11 | import marked from 'marked'; 12 | import highlight from 'highlight.js'; 13 | import { DomSanitizer } from '@angular/platform-browser'; 14 | 15 | @Directive({ 16 | selector: '[previewHeight]' 17 | }) 18 | export class CalculationContentHeightDirective { 19 | constructor( 20 | el: ElementRef 21 | ) { 22 | el.nativeElement.style.height = window.innerHeight - 170 + 'px'; 23 | el.nativeElement.style.maxHeight = window.innerHeight - 170 + 'px'; 24 | el.nativeElement.style.overflow = 'auto'; 25 | } 26 | } 27 | 28 | @Component({ 29 | selector: 'app-add-link-note', 30 | templateUrl: './add-link-note.component.html', 31 | styleUrls: ['./add-link-note.component.scss'], 32 | encapsulation: ViewEncapsulation.None 33 | }) 34 | export class AddLinkNoteComponent implements OnInit, OnDestroy { 35 | dropdownMenuSub: Subscription; 36 | isShowMarkdownEditor = false; 37 | dropdownMenu = []; 38 | labelList = []; 39 | noteTitle = ''; 40 | noteContent = ''; 41 | sourceLink = ''; 42 | constructor( 43 | private tagService: TagService, 44 | private loadingBar: LoadingBarService, 45 | private http: HttpClient, 46 | private sanitizer: DomSanitizer, 47 | private noteService: NoteService, 48 | private msg: MsgService, 49 | private router: Router 50 | ) { } 51 | 52 | ngOnInit() { 53 | this.dropdownMenuSub = this.tagService.tagList$.subscribe((data) => { 54 | this.dropdownMenu = data; 55 | }); 56 | } 57 | 58 | ngOnDestroy() { 59 | this.dropdownMenuSub.unsubscribe(); 60 | } 61 | 62 | onEnter(value) { 63 | this.sourceLink = value; 64 | this.loadingBar.$Loading.start(); 65 | this.http.post('/api/generateNote', { 66 | link: value 67 | }) 68 | .subscribe((data) => { 69 | 70 | marked(data['content'], function (err, content) { 71 | if (err) { 72 | throw err; 73 | } 74 | }); 75 | 76 | this.loadingBar.$Loading.finish(); 77 | this.isShowMarkdownEditor = true; 78 | this.noteTitle = data['title']; 79 | this.noteContent = data['content']; 80 | }); 81 | } 82 | 83 | // 保存笔记 84 | save() { 85 | if (this.noteTitle === '' || this.noteContent === '' || this.labelList.length === 0) { 86 | this.msg.info('请输入完整的笔记信息!'); 87 | } else { 88 | const sub = this.noteService._addNote({ 89 | title: this.noteTitle, 90 | content: this.noteContent, 91 | tag: this.labelList, 92 | date: new Date(), 93 | sourceLink: this.sourceLink 94 | }).subscribe((res) => { 95 | if (res['code'] === 200) { 96 | this.msg.info('保存成功!'); 97 | this.noteService._updateAllNote(); 98 | localStorage.setItem('noteItemInfo', JSON.stringify(res['data'])); 99 | this.router.navigate(['/viewNote']); 100 | } 101 | }); 102 | } 103 | } 104 | 105 | selectItem(data) { 106 | this.labelList.push(data); 107 | } 108 | 109 | delectLabelItem(index) { 110 | this.labelList.splice(index, 1); 111 | } 112 | 113 | cancel() { 114 | this.isShowMarkdownEditor = false; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/app/page/add-note/add-note.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 |
8 | 9 |
10 |
    11 |
  • 12 |
    {{item.name}}
    13 | 14 |
  • 15 |
16 |
17 |
18 | 保存 19 |
20 |
21 |
22 | 23 |
24 |
-------------------------------------------------------------------------------- /src/app/page/add-note/add-note.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../directives/markdown-editor/simplemde.min.scss'; 2 | .add-note-wrap { 3 | margin: 15px 10%; 4 | padding: 1rem; 5 | background-color: #fff; 6 | box-shadow: 0 1px 6px #ccc; 7 | border-radius: 3px; 8 | } 9 | .editor-toolbar{ 10 | color: #ccc; 11 | } 12 | .add-note-title{ 13 | height: 35px; 14 | display: flex; 15 | justify-content: center; 16 | align-items: center; 17 | input{ 18 | text-align: center; 19 | width: 100%; 20 | height: 30px; 21 | border: none; 22 | outline: none; 23 | font-size: 20px; 24 | color:#4e4e4e; 25 | } 26 | } 27 | ::-webkit-input-placeholder { /* WebKit browsers */ 28 | color:#999; 29 | } 30 | .add-note-toolbar{ 31 | width: 100%; 32 | display: flex; 33 | align-items: center; 34 | padding: 10px 0; 35 | border-bottom: 1px solid #ececec; 36 | } 37 | .add-note-toolbar-label{ 38 | width: 80%; 39 | height: 32px; 40 | line-height: 32px; 41 | } 42 | .add-note-toolbar-dropdown{ 43 | width: 150px; 44 | float: left; 45 | padding-top: 8px; 46 | } 47 | .tag-list-wrap{ 48 | width: 70%; 49 | max-width: 70%; 50 | display: flex; 51 | flex-wrap: wrap; 52 | height: 32px; 53 | max-height: 32px; 54 | overflow: scroll; 55 | &::-webkit-scrollbar{ 56 | width: 0; 57 | height: 0; 58 | } 59 | .tag-item{ 60 | height: 25px; 61 | line-height: 25px; 62 | border: 1px solid #ccc; 63 | border-radius: 4px; 64 | margin: 3px 3px; 65 | display: flex; 66 | flex-wrap: wrap; 67 | cursor: pointer; 68 | div{ 69 | line-height: 25px; 70 | padding: 0 14px; 71 | }; 72 | i{ 73 | line-height: 25px; 74 | padding: 0 6px; 75 | border-left: 1px solid #ccc; 76 | font-size: 12px; 77 | color: #ccc; 78 | }; 79 | i:hover{ 80 | color: #808b96; 81 | } 82 | &:hover{ 83 | box-shadow: 1px 1px 4px rgba(0,0,0,.2); 84 | } 85 | } 86 | } 87 | .add-note-toolbar-btn{ 88 | width: 20%; 89 | align-self: flex-end; 90 | app-button{ 91 | float: right; 92 | margin-left: 5px; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/app/page/add-note/add-note.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AddNoteComponent } from './add-note.component'; 4 | 5 | describe('AddNoteComponent', () => { 6 | let component: AddNoteComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ AddNoteComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AddNoteComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/page/add-note/add-note.component.ts: -------------------------------------------------------------------------------- 1 | import { Router } from '@angular/router'; 2 | import { MsgService } from './../../services/msg/msg.service'; 3 | import { Subscription } from 'rxjs/Subscription'; 4 | import { NoteService } from '../../services/note/note.service'; 5 | import { TagService } from './../../services/tag/tag.service'; 6 | import { Component, OnInit, ViewEncapsulation, OnDestroy } from '@angular/core'; 7 | import { dropdownItem } from '../../component/dropdown/dropdown.component'; 8 | 9 | @Component({ 10 | selector: 'app-add-note', 11 | templateUrl: './add-note.component.html', 12 | styleUrls: ['./add-note.component.scss'], 13 | encapsulation: ViewEncapsulation.None 14 | }) 15 | export class AddNoteComponent implements OnInit, OnDestroy { 16 | dropdownMenuSub: Subscription; 17 | dropdownMenu = []; 18 | title = ''; 19 | content = ''; 20 | tagList = []; 21 | 22 | constructor( 23 | private tagService: TagService, 24 | private noteService: NoteService, 25 | private msg: MsgService, 26 | private router: Router 27 | ) { } 28 | 29 | ngOnInit() { 30 | this.dropdownMenuSub = this.tagService.tagList$.subscribe((data) => { 31 | this.dropdownMenu = data; 32 | }); 33 | } 34 | 35 | ngOnDestroy() { 36 | this.dropdownMenuSub.unsubscribe(); 37 | } 38 | 39 | selectItem(data) { 40 | this.tagList.push(data); 41 | } 42 | 43 | delectLabelItem(index) { 44 | this.tagList.splice(index, 1); 45 | } 46 | 47 | markdownValueChange(data) { 48 | this.content = data; 49 | } 50 | 51 | // 保存笔记 52 | save() { 53 | if (this.title === '' || this.content === '' || this.tagList.length === 0) { 54 | this.msg.info('请输入完整的笔记信息!'); 55 | } else { 56 | const sub = this.noteService._addNote({ 57 | title: this.title, 58 | content: this.content, 59 | tag: this.tagList, 60 | date: new Date(), 61 | sourceLink: '' 62 | }).subscribe((res) => { 63 | if (res['code'] === 200) { 64 | this.msg.info('保存成功!'); 65 | this.noteService._updateAllNote(); 66 | localStorage.setItem('noteItemInfo', JSON.stringify(res['data'])); 67 | this.router.navigate(['/viewNote']); 68 | } 69 | }); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/app/page/classification/classification.component.html: -------------------------------------------------------------------------------- 1 |
2 |
    3 |
  • 全部
  • 4 |
  • 5 | {{item.name}} 6 |
  • 7 |
8 |
9 |
10 |
11 | {{item.title}} 12 |
13 |
14 | {{item.date | date:"yyyy-MM-dd HH:mm:ss"}} 15 |
16 |
17 | {{item.content | filterNoteContent}} 18 |
19 |
20 |
21 | 该标签分类下暂无内容 22 |
23 |
24 |
-------------------------------------------------------------------------------- /src/app/page/classification/classification.component.scss: -------------------------------------------------------------------------------- 1 | .classification-wrap{ 2 | margin: 0 3rem; 3 | padding: 1rem; 4 | 5 | .tabs{ 6 | max-width: 100%; 7 | overflow: auto; 8 | border-bottom: 1px solid #d9d9d9; 9 | display: flex; 10 | &::-webkit-scrollbar{ 11 | width: 0px; 12 | height: 0px; 13 | }; 14 | li{ 15 | font-size: 14px; 16 | line-height: 1.5; 17 | padding: 10px 15px; 18 | text-align: center; 19 | cursor: pointer; 20 | color: #919191; 21 | white-space: nowrap; 22 | &:hover{ 23 | color: #404040; 24 | } 25 | }; 26 | .tab-active{ 27 | border-bottom: 2px solid #ccc; 28 | color: #404040; 29 | } 30 | }; 31 | 32 | .tab-content{ 33 | transition: opacity 1s; 34 | display: flex; 35 | flex-wrap: wrap; 36 | align-items: flex-start; 37 | align-content: flex-start; 38 | width: 100%; 39 | &::-webkit-scrollbar{ 40 | width: 0px; 41 | } 42 | .note-item{ 43 | width: 28%; 44 | height: 180px; 45 | background-color: #fff; 46 | box-shadow: 0 1px 6px #ccc; 47 | border-radius: 3px; 48 | margin: 1% 1%; 49 | padding: 1%; 50 | text-align: center; 51 | .title{ 52 | font-size: 16px; 53 | color: #484848; 54 | cursor: pointer; 55 | height: 24px; 56 | max-height: 24px; 57 | overflow: hidden; 58 | white-space: nowrap; 59 | text-overflow: ellipsis; 60 | } 61 | .date{ 62 | color: #aaa; 63 | font-size: 12px; 64 | padding: 10px 0; 65 | border-bottom: 1px dashed #ddd; 66 | } 67 | .content{ 68 | text-align: left; 69 | padding: 10px 0; 70 | color: #666; 71 | height: 5rem; 72 | max-height: 5rem; 73 | line-height: 1.2rem; 74 | margin-bottom: 1rem; 75 | text-overflow: ellipsis; 76 | display: -webkit-box; 77 | -webkit-box-orient: vertical; 78 | -webkit-line-clamp: 5; 79 | overflow: hidden; 80 | } 81 | &:hover{ 82 | cursor: pointer; 83 | transform: scale(1.02, 1.02) 84 | } 85 | } 86 | .tip{ 87 | width: 100%; 88 | text-align: center; 89 | font-size: 30px; 90 | color: #ccc; 91 | margin-top: 20vh; 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /src/app/page/classification/classification.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ClassificationComponent } from './classification.component'; 4 | 5 | describe('ClassificationComponent', () => { 6 | let component: ClassificationComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ClassificationComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ClassificationComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/page/classification/classification.component.ts: -------------------------------------------------------------------------------- 1 | import { style } from '@angular/animations'; 2 | import { NoteService } from '../../services/note/note.service'; 3 | import { Subscription } from 'rxjs/Subscription'; 4 | import { TagService } from '../../services/tag/tag.service'; 5 | import { Component, OnInit, Directive, ElementRef, Pipe, PipeTransform, OnDestroy } from '@angular/core'; 6 | import { Router } from '@angular/router'; 7 | 8 | @Directive({ 9 | selector: '[classificationTabsContentHeight]' 10 | }) 11 | export class ClassificationTabsContentHeightDirective { 12 | constructor(el: ElementRef) { 13 | el.nativeElement.style.height = window.innerHeight - 80 + 'px'; 14 | el.nativeElement.style.maxHeight = window.innerHeight - 80 + 'px'; 15 | el.nativeElement.style.overflow = 'auto'; 16 | } 17 | } 18 | 19 | @Pipe({name: 'filterNoteContent'}) 20 | export class FilterNoteContentPipe implements PipeTransform { 21 | transform(value: String): String { 22 | return value.replace(/[^\u4e00-\u9fa5]/gi, ''); 23 | } 24 | } 25 | 26 | @Component({ 27 | selector: 'app-classification', 28 | templateUrl: './classification.component.html', 29 | styleUrls: ['./classification.component.scss'] 30 | }) 31 | export class ClassificationComponent implements OnInit, OnDestroy { 32 | tagListSub: Subscription; 33 | tagList = []; 34 | content = '全部'; 35 | tabIsActive = true; 36 | allNoteSub: Subscription; 37 | allNote = []; 38 | currentNoteList = []; 39 | 40 | constructor( 41 | private tagService: TagService, 42 | private noteService: NoteService, 43 | private router: Router 44 | ) { 45 | } 46 | 47 | ngOnInit() { 48 | this.tagListSub = this.tagService.tagList$.subscribe((data) => { 49 | this.tagList = data; 50 | }); 51 | 52 | this._activeFalse(); 53 | 54 | this.allNoteSub = this.noteService.allNote$.subscribe((data) => { 55 | this.allNote = data; 56 | this.currentNoteList = data; 57 | }); 58 | } 59 | 60 | ngOnDestroy() { 61 | this.tagListSub.unsubscribe(); 62 | this.allNoteSub.unsubscribe(); 63 | } 64 | 65 | _activeFalse() { 66 | this.tagList.map((item) => { 67 | item['tabIsActive'] = false; 68 | }); 69 | } 70 | 71 | all() { 72 | this._activeFalse(); 73 | this.tabIsActive = true; 74 | this.content = '全部'; 75 | this.currentNoteList = this.allNote; 76 | } 77 | 78 | tabClick(data) { 79 | this.tabIsActive = false; 80 | this._activeFalse(); 81 | data.tabIsActive = true; 82 | 83 | const temporary = []; 84 | this.allNote.forEach((item, index) => { 85 | const jsonStringify = JSON.stringify(item.tag); 86 | if (jsonStringify.indexOf(data.name) !== -1) { 87 | temporary.push(item); 88 | } 89 | }); 90 | this.currentNoteList = temporary; 91 | } 92 | 93 | viewNote(data) { 94 | this.router.navigate(['/viewNote']); 95 | localStorage.setItem('noteItemInfo', JSON.stringify(data)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/app/page/edit-note/edit-note.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 |
8 | 9 |
10 |
    11 |
  • 12 |
    {{item.name}}
    13 | 14 |
  • 15 |
16 |
17 |
18 | 保存 19 | 取消 20 |
21 |
22 |
23 | 24 |
25 |
-------------------------------------------------------------------------------- /src/app/page/edit-note/edit-note.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../directives/markdown-editor/simplemde.min.scss'; 2 | .add-note-wrap { 3 | margin: 15px 10%; 4 | padding: 1rem; 5 | background-color: #fff; 6 | box-shadow: 0 1px 6px #ccc; 7 | border-radius: 3px; 8 | } 9 | .editor-toolbar{ 10 | color: #ccc; 11 | } 12 | .add-note-title{ 13 | height: 35px; 14 | display: flex; 15 | justify-content: center; 16 | align-items: center; 17 | input{ 18 | text-align: center; 19 | width: 100%; 20 | height: 30px; 21 | border: none; 22 | outline: none; 23 | font-size: 20px; 24 | color:#4e4e4e; 25 | } 26 | } 27 | ::-webkit-input-placeholder { /* WebKit browsers */ 28 | color:#999; 29 | } 30 | .add-note-toolbar{ 31 | width: 100%; 32 | display: flex; 33 | align-items: center; 34 | padding: 10px 0; 35 | border-bottom: 1px solid #ececec; 36 | } 37 | .add-note-toolbar-label{ 38 | width: 80%; 39 | height: 32px; 40 | line-height: 32px; 41 | } 42 | .add-note-toolbar-dropdown{ 43 | width: 150px; 44 | float: left; 45 | padding-top: 8px; 46 | } 47 | .tag-list-wrap{ 48 | width: 70%; 49 | max-width: 70%; 50 | display: flex; 51 | flex-wrap: wrap; 52 | height: 32px; 53 | max-height: 32px; 54 | overflow: scroll; 55 | &::-webkit-scrollbar{ 56 | width: 0; 57 | height: 0; 58 | } 59 | .tag-item{ 60 | height: 25px; 61 | line-height: 25px; 62 | border: 1px solid #ccc; 63 | border-radius: 4px; 64 | margin: 3px 3px; 65 | display: flex; 66 | flex-wrap: wrap; 67 | cursor: pointer; 68 | div{ 69 | line-height: 25px; 70 | padding: 0 14px; 71 | }; 72 | i{ 73 | line-height: 25px; 74 | padding: 0 6px; 75 | border-left: 1px solid #ccc; 76 | font-size: 12px; 77 | color: #ccc; 78 | }; 79 | i:hover{ 80 | color: #808b96; 81 | } 82 | &:hover{ 83 | box-shadow: 1px 1px 4px rgba(0,0,0,.2); 84 | } 85 | } 86 | } 87 | .add-note-toolbar-btn{ 88 | width: 20%; 89 | align-self: flex-end; 90 | app-button{ 91 | float: right; 92 | margin-left: 5px; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/app/page/edit-note/edit-note.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { EditNoteComponent } from './edit-note.component'; 4 | 5 | describe('EditNoteComponent', () => { 6 | let component: EditNoteComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ EditNoteComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(EditNoteComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/page/edit-note/edit-note.component.ts: -------------------------------------------------------------------------------- 1 | import { Router } from '@angular/router'; 2 | import { Subscription } from 'rxjs/Subscription'; 3 | import { Component, OnInit, ViewEncapsulation, OnDestroy } from '@angular/core'; 4 | import { TagService } from '../../services/tag/tag.service'; 5 | import { NoteService } from '../../services/note/note.service'; 6 | import { MsgService } from '../../services/msg/msg.service'; 7 | 8 | @Component({ 9 | selector: 'app-edit-note', 10 | templateUrl: './edit-note.component.html', 11 | styleUrls: ['./edit-note.component.scss'], 12 | encapsulation: ViewEncapsulation.None 13 | }) 14 | export class EditNoteComponent implements OnInit, OnDestroy { 15 | dropdownMenuSub: Subscription; 16 | dropdownMenu = []; 17 | title = ''; 18 | content = ''; 19 | tagList = []; 20 | noteInfo = { 21 | content: String, 22 | date: String, 23 | sourceLink: String, 24 | tag: [], 25 | title: '', 26 | __v: Number, 27 | _id: '' 28 | }; 29 | 30 | constructor( 31 | private tagService: TagService, 32 | private noteService: NoteService, 33 | private msg: MsgService, 34 | private router: Router 35 | ) { } 36 | 37 | ngOnInit() { 38 | this.dropdownMenuSub = this.tagService.tagList$.subscribe((data) => { 39 | this.dropdownMenu = data; 40 | }); 41 | 42 | this.noteInfo = JSON.parse(localStorage.getItem('noteItemInfo')); 43 | this.tagList = this.noteInfo.tag; 44 | this.title = this.noteInfo.title; 45 | } 46 | 47 | ngOnDestroy() { 48 | this.dropdownMenuSub.unsubscribe(); 49 | } 50 | 51 | selectItem(data) { 52 | this.tagList.push(data); 53 | } 54 | 55 | delectLabelItem(index) { 56 | this.tagList.splice(index, 1); 57 | } 58 | 59 | markdownValueChange(data) { 60 | this.content = data; 61 | } 62 | 63 | // 保存笔记 64 | save() { 65 | if (this.title === '' || this.content === '' || this.tagList.length === 0) { 66 | this.msg.info('请输入完整的笔记信息!'); 67 | } else { 68 | const sub = this.noteService._modifyNote({ 69 | title: this.title, 70 | content: this.content, 71 | tag: this.tagList, 72 | date: new Date(), 73 | sourceLink: '', 74 | _id: this.noteInfo._id 75 | }).subscribe((res) => { 76 | if (res['code'] === 200) { 77 | this.msg.info('修改成功!'); 78 | this.noteService._updateAllNote(); 79 | localStorage.setItem('noteItemInfo', JSON.stringify(res['data'])); 80 | this.router.navigate(['/viewNote']); 81 | } 82 | }); 83 | } 84 | } 85 | 86 | cancel() { 87 | this.router.navigate(['/viewNote']); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/app/page/index/index.component.html: -------------------------------------------------------------------------------- 1 | 13 |
14 | 15 | 16 | 17 |
-------------------------------------------------------------------------------- /src/app/page/index/index.component.scss: -------------------------------------------------------------------------------- 1 | .login-wrap { 2 | width: 100vw; 3 | height: 100vh; 4 | background-color: #fff; 5 | position: fixed; 6 | display: block; 7 | top: 0; 8 | left: 0; 9 | z-index: 100; 10 | .login-title { 11 | position: absolute; 12 | top: 50%; 13 | width: 100%; 14 | margin-top: -230px; 15 | text-align: center; 16 | font-size: 40px; 17 | color: #3db8c1; 18 | img{ 19 | width: 50px; 20 | height: 50px; 21 | } 22 | } 23 | .login-panel { 24 | position: absolute; 25 | left: 50%; 26 | top: 50%; 27 | width: 300px; 28 | height: 160px; 29 | margin: -150px 0 0 -190px; 30 | padding: 40px; 31 | border-radius: 5px; 32 | background: #f5f5f5; 33 | text-align: center; 34 | .login-name { 35 | margin-bottom: 1rem; 36 | -webkit-appearance: none; 37 | -moz-appearance: none; 38 | appearance: none; 39 | background-color: #fff; 40 | background-image: none; 41 | border-radius: 4px; 42 | border: 1px solid #bfcbd9; 43 | box-sizing: border-box; 44 | color: #1f2d3d; 45 | display: block; 46 | font-size: inherit; 47 | height: 36px; 48 | line-height: 1; 49 | outline: 0; 50 | padding: 3px 10px; 51 | transition: border-color .2s cubic-bezier(.645,.045,.355,1); 52 | width: 100%; 53 | } 54 | .login-pwd { 55 | margin-bottom: 1rem; 56 | -webkit-appearance: none; 57 | -moz-appearance: none; 58 | appearance: none; 59 | background-color: #fff; 60 | background-image: none; 61 | border-radius: 4px; 62 | border: 1px solid #bfcbd9; 63 | box-sizing: border-box; 64 | color: #1f2d3d; 65 | display: block; 66 | font-size: inherit; 67 | height: 36px; 68 | line-height: 1; 69 | outline: 0; 70 | padding: 3px 10px; 71 | transition: border-color .2s cubic-bezier(.645,.045,.355,1); 72 | width: 100%; 73 | } 74 | .login-submit { 75 | width: 300px; 76 | color: #fff; 77 | display: inline-block; 78 | line-height: 1; 79 | white-space: nowrap; 80 | cursor: pointer; 81 | background: #fff; 82 | border: none; 83 | -webkit-appearance: none; 84 | text-align: center; 85 | box-sizing: border-box; 86 | outline: 0; 87 | margin: 0; 88 | padding: 10px 15px; 89 | font-size: 14px; 90 | border-radius: 4px; 91 | background-color: #3db8c1; 92 | } 93 | .case-number { 94 | margin-top: 1rem; 95 | font-size: 14px; 96 | color: #5d5d5d; 97 | } 98 | } 99 | } 100 | 101 | .github{ 102 | position: fixed; 103 | top: 10px; 104 | right: -40px; 105 | width: 120px; 106 | height: 30px; 107 | background-color: #3db8c1; 108 | transform: rotate(45deg); 109 | z-index: 101; 110 | a{ 111 | color: #000; 112 | } 113 | i{ 114 | font-size: 29px; 115 | position: fixed; 116 | top: -6px; 117 | right: 51px; 118 | z-index: 101; 119 | cursor: pointer; 120 | &:hover{ 121 | transform: scale(1.05, 1.05) 122 | } 123 | } 124 | } 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /src/app/page/index/index.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { IndexComponent } from './index.component'; 4 | 5 | describe('IndexComponent', () => { 6 | let component: IndexComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ IndexComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(IndexComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/page/index/index.component.ts: -------------------------------------------------------------------------------- 1 | import { Router } from '@angular/router'; 2 | import { MsgService } from './../../services/msg/msg.service'; 3 | import { HttpClient } from '@angular/common/http'; 4 | import { Component, OnInit } from '@angular/core'; 5 | 6 | @Component({ 7 | selector: 'app-index', 8 | templateUrl: './index.component.html', 9 | styleUrls: ['./index.component.scss'] 10 | }) 11 | export class IndexComponent implements OnInit { 12 | name = ''; 13 | pwd = ''; 14 | 15 | constructor( 16 | private http: HttpClient, 17 | private msg: MsgService, 18 | private router: Router 19 | ) { } 20 | 21 | ngOnInit() { 22 | 23 | } 24 | 25 | login() { 26 | this.http.post('/api/login', { 27 | name: this.name, 28 | pwd: this.pwd 29 | }).subscribe((res) => { 30 | if (res['code'] !== 200) { 31 | this.msg.info( res['msg'] ); 32 | } else { 33 | this.router.navigate(['/addLinkNote']); 34 | localStorage.setItem('userName', this.name); 35 | } 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/page/search/search.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 |
8 | {{item.title}} 9 |
10 |
11 | {{item.date | date:"yyyy-MM-dd HH:mm:ss"}} 12 |
13 |
14 | {{item.content | filterNoteContent}} 15 |
16 |
17 |
18 |
-------------------------------------------------------------------------------- /src/app/page/search/search.component.scss: -------------------------------------------------------------------------------- 1 | .search-wrap{ 2 | height: 100vh; 3 | padding: 1vh 8vh 30vh 8vh; 4 | .input-wrap{ 5 | height:52px; 6 | line-height:52px; 7 | margin-top: 19vh; 8 | padding-bottom: 3rem; 9 | border-bottom: 1px solid #ececec; 10 | .search-input{ 11 | border-bottom: 1px solid #ccc; 12 | background-color: transparent; 13 | margin-bottom: 3vh; 14 | border: none; 15 | color: #ccc; 16 | height: 52px; 17 | width: 100%; 18 | overflow-x: scroll; 19 | font-size: 40px; 20 | outline: none; 21 | } 22 | } 23 | 24 | .tab-content{ 25 | transition: opacity 1s; 26 | display: flex; 27 | flex-wrap: wrap; 28 | align-items: flex-start; 29 | align-content: flex-start; 30 | width: 100%; 31 | &::-webkit-scrollbar{ 32 | width: 0px; 33 | } 34 | .note-item{ 35 | width: 28%; 36 | height: 180px; 37 | background-color: #f7f7f7; 38 | box-shadow: 0 1px 6px #ccc; 39 | border-radius: 3px; 40 | margin: 1% 1%; 41 | padding: 1%; 42 | text-align: center; 43 | .title{ 44 | font-size: 16px; 45 | color: #484848; 46 | cursor: pointer; 47 | height: 24px; 48 | max-height: 24px; 49 | overflow: hidden; 50 | white-space: nowrap; 51 | text-overflow: ellipsis; 52 | } 53 | .date{ 54 | color: #aaa; 55 | font-size: 12px; 56 | padding: 10px 0; 57 | border-bottom: 1px dashed #ddd; 58 | } 59 | .content{ 60 | text-align: left; 61 | padding: 10px 0; 62 | color: #666; 63 | height: 5rem; 64 | max-height: 5rem; 65 | line-height: 1.2rem; 66 | margin-bottom: 1rem; 67 | text-overflow: ellipsis; 68 | display: -webkit-box; 69 | -webkit-box-orient: vertical; 70 | -webkit-line-clamp: 5; 71 | overflow: hidden; 72 | } 73 | &:hover{ 74 | transform: scale(1.02, 1.02); 75 | cursor: pointer; 76 | } 77 | } 78 | .tip{ 79 | width: 100%; 80 | text-align: center; 81 | font-size: 30px; 82 | color: #ccc; 83 | margin-top: 20vh; 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /src/app/page/search/search.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SearchComponent } from './search.component'; 4 | 5 | describe('SearchComponent', () => { 6 | let component: SearchComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ SearchComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SearchComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/page/search/search.component.ts: -------------------------------------------------------------------------------- 1 | import { Router } from '@angular/router'; 2 | import { Subscription } from 'rxjs/Subscription'; 3 | import { NoteService } from '../../services/note/note.service'; 4 | import { Component, OnInit, OnDestroy } from '@angular/core'; 5 | 6 | @Component({ 7 | selector: 'app-search', 8 | templateUrl: './search.component.html', 9 | styleUrls: ['./search.component.scss'] 10 | }) 11 | export class SearchComponent implements OnInit, OnDestroy { 12 | allNoteSub: Subscription; 13 | allNote = []; 14 | resultNoteList = []; 15 | searchName = ''; 16 | 17 | constructor( 18 | private noteService: NoteService, 19 | private router: Router 20 | ) { } 21 | 22 | ngOnInit() { 23 | this.allNoteSub = this.noteService.allNote$.subscribe((data) => { 24 | this.allNote = data; 25 | }); 26 | } 27 | 28 | ngOnDestroy() { 29 | this.allNoteSub.unsubscribe(); 30 | } 31 | 32 | onEnter(name) { 33 | const temporary = []; 34 | this.allNote.forEach((item, index) => { 35 | const jsonStringify = JSON.stringify(item.tag) + JSON.stringify(item.title); 36 | if (jsonStringify.indexOf(name) !== -1) { 37 | temporary.push(item); 38 | } 39 | }); 40 | this.resultNoteList = temporary; 41 | this.searchName = ''; 42 | } 43 | 44 | viewNote(data) { 45 | this.router.navigate(['/viewNote']); 46 | localStorage.setItem('noteItemInfo', JSON.stringify(data)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/app/page/tag/tag.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 |
{{item.name}}
8 | 9 |
10 |
11 |
-------------------------------------------------------------------------------- /src/app/page/tag/tag.component.scss: -------------------------------------------------------------------------------- 1 | .tag{ 2 | height: 100vh; 3 | padding: 1vh 8vh 30vh 8vh; 4 | } 5 | .input-wrap{ 6 | height:52px; 7 | line-height:52px; 8 | margin-top: 19vh; 9 | margin-bottom: 20px; 10 | padding-bottom: 30px; 11 | border-bottom: 1px solid #ccc; 12 | } 13 | .search-input{ 14 | border-bottom: 1px solid #ccc; 15 | background-color: transparent; 16 | margin-bottom: 3vh; 17 | border: none; 18 | color: #ccc; 19 | height: 52px; 20 | width: 100%; 21 | overflow-x: scroll; 22 | font-size: 40px; 23 | outline: none; 24 | } 25 | .tag-list-wrap{ 26 | display: flex; 27 | flex-wrap: wrap; 28 | overflow: auto; 29 | max-height: 61vh; 30 | .tag-item{ 31 | height: 25px; 32 | line-height: 25px; 33 | border: 1px solid #ccc; 34 | border-radius: 4px; 35 | margin: 3px 3px; 36 | display: flex; 37 | flex-wrap: wrap; 38 | cursor: pointer; 39 | div{ 40 | line-height: 25px; 41 | padding: 0 14px; 42 | }; 43 | i{ 44 | line-height: 25px; 45 | padding: 0 6px; 46 | border-left: 1px solid #ccc; 47 | font-size: 12px; 48 | color: #ccc; 49 | }; 50 | i:hover{ 51 | color: #808b96; 52 | } 53 | &:hover{ 54 | box-shadow: 1px 1px 4px rgba(0,0,0,.2); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/app/page/tag/tag.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { TagComponent } from './tag.component'; 4 | 5 | describe('TagComponent', () => { 6 | let component: TagComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ TagComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(TagComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/page/tag/tag.component.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { MsgService } from './../../services/msg/msg.service'; 3 | import { TagService } from './../../services/tag/tag.service'; 4 | import { Component, OnInit, OnDestroy } from '@angular/core'; 5 | import { Subscription } from 'rxjs/Subscription'; 6 | 7 | @Component({ 8 | selector: 'app-tag', 9 | templateUrl: './tag.component.html', 10 | styleUrls: ['./tag.component.scss'] 11 | }) 12 | export class TagComponent implements OnInit , OnDestroy { 13 | tagListSub: Subscription; 14 | tagList = []; 15 | tagName = ''; 16 | constructor( 17 | private tagService: TagService, 18 | private msg: MsgService, 19 | private http: HttpClient 20 | ) { 21 | } 22 | 23 | ngOnInit() { 24 | this.tagListSub = this.tagService.tagList$.subscribe((data) => { 25 | this.tagList = data; 26 | }); 27 | 28 | } 29 | 30 | ngOnDestroy() { 31 | this.tagListSub.unsubscribe(); 32 | } 33 | 34 | addTag(value) { 35 | if (value !== '') { 36 | this.tagService._addTag(value); 37 | this.tagName = ''; 38 | } else { 39 | this.msg.info('标签名字不能为空!'); 40 | } 41 | } 42 | 43 | delectTagItem(id) { 44 | this.tagService._deleteTag(id); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/page/view-note/view-note.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{noteInfo.title}} 4 |
5 |
6 |
7 | 8 | 9 |
10 |
11 | {{noteInfo.date | date:"yyyy-MM-dd HH:mm:ss"}} 12 |
13 |
    14 |
  • 15 | {{item.name}} 16 |
  • 17 |
18 | 19 | {{noteInfo.sourceLink}} 20 | 21 |
22 |
23 |
24 | 25 |
26 |

目录

27 | 32 |
33 | 34 |
35 |
36 |
37 | 38 |
39 |
40 | 删除笔记 41 |
42 |
43 | 确定删除《{{noteInfo.title}}》笔记吗? 44 |
45 |
46 | 47 | 48 |
49 |
50 |
-------------------------------------------------------------------------------- /src/app/page/view-note/view-note.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../assets/ionicons.min.css'; /* 图标库 */ 2 | .view-note-wrap{ 3 | float: left; 4 | width: 70%; 5 | margin: 15px 25px 15px 65px; 6 | padding: 1rem; 7 | background-color: #fff; 8 | box-shadow: 0 1px 6px #ccc; 9 | border-radius: 3px; 10 | .view-note-title{ 11 | text-align: center; 12 | height: 35px; 13 | font-size: 20px; 14 | width: 100%; 15 | max-width: 100%; 16 | overflow: hidden; 17 | text-overflow: ellipsis; 18 | white-space: nowrap; 19 | } 20 | .note-info{ 21 | border-bottom: 1px #ccc dashed; 22 | padding: 10px; 23 | display: flex; 24 | position: relative; 25 | .edit{ 26 | height: 25px; 27 | line-height: 30px; 28 | color: #999999; 29 | font-size: 16px; 30 | i{ 31 | margin-right: 25px; 32 | cursor: pointer; 33 | } 34 | } 35 | .note-time{ 36 | height: 25px; 37 | line-height: 30px; 38 | color: #999999; 39 | } 40 | .note-tag{ 41 | display: flex; 42 | -ms-flex-wrap: wrap; 43 | flex-wrap: wrap; 44 | margin: 0; 45 | list-style: none; 46 | padding-left: 10px; 47 | li{ 48 | min-width: 40px; 49 | text-align: center; 50 | height: 20px; 51 | line-height: 20px; 52 | border: 1px solid #ccc; 53 | border-radius: 4px; 54 | margin: 3px 3px; 55 | cursor: pointer; 56 | color: #919191; 57 | padding: 0 5px; 58 | } 59 | } 60 | .sourceLink{ 61 | width: 170px; 62 | max-width: 170px; 63 | overflow: hidden; 64 | text-overflow: ellipsis; 65 | white-space: nowrap; 66 | line-height: 30px; 67 | height: 25px; 68 | position: absolute; 69 | right: 0; 70 | color: #3db8c1; 71 | cursor: pointer; 72 | } 73 | } 74 | 75 | .preview{ 76 | font-size: 14px !important; 77 | padding: 10px 30px; 78 | background-color: #fbfbfb; 79 | &::-webkit-scrollbar{ 80 | width: 0; 81 | } 82 | img{ 83 | max-width: 100%; 84 | } 85 | th, td{ 86 | border: 1px solid #E6E6E6; 87 | padding: 5px 8px; 88 | } 89 | table{ 90 | border-collapse: collapse; 91 | } 92 | blockquote{ 93 | margin: 1em 0; 94 | border-left: 4px solid #3db8c1; 95 | padding: 10px; 96 | color: #666; 97 | background-color: #eeeeee; 98 | p { 99 | line-height: 2; 100 | display: block; 101 | margin-bottom: 0px; 102 | a { 103 | color: #3db8c1; 104 | text-decoration: none; 105 | } 106 | } 107 | ul{ 108 | list-style-type: none; 109 | padding: 0; 110 | margin: 0; 111 | } 112 | } 113 | h1{ 114 | margin: 1.3rem 0; 115 | line-height: 1.2; 116 | font-size: 2em; 117 | } 118 | h2{ 119 | border-bottom: 1px solid #eee; 120 | padding-bottom: 10px; 121 | } 122 | h3{ 123 | margin: 20px 0; 124 | } 125 | p{ 126 | line-height: 2; 127 | display: block; 128 | margin-bottom: 20px; 129 | a{ 130 | color: #3db8c1; 131 | text-decoration: none; 132 | } 133 | video{ 134 | width: 100%; 135 | } 136 | } 137 | ul{ 138 | padding-left: 2.7rem; 139 | display: block; 140 | list-style-type: disc; 141 | li{ 142 | margin-bottom: .6rem; 143 | list-style: inherit; 144 | } 145 | } 146 | code{ 147 | font-family: Menlo,Monaco,Consolas,Courier New,monospace; 148 | font-size: 1rem; 149 | padding: 2px 5px; 150 | word-break: break-word; 151 | color: #49a9ee; 152 | background-color: #f8f8f8; 153 | border-radius: 2px; 154 | overflow-x: scroll; 155 | font-size: 12px; 156 | margin: 0 4px; 157 | } 158 | pre{ 159 | white-space: pre; 160 | margin: 1em 0px; 161 | padding: 5px; 162 | width: 100%; 163 | max-width: 100%; 164 | overflow: scroll; 165 | border-radius: 3px; 166 | background-color: #eeeeee; 167 | code{ 168 | color: #333 !important; 169 | background-color: #eeeeee; 170 | .hljs-function{ 171 | color: #333; 172 | } 173 | .hljs-keyword{ 174 | color: #333; 175 | font-weight: 700; 176 | } 177 | .hljs-title{ 178 | color: #900; 179 | font-weight: 700; 180 | } 181 | .hljs-params{ 182 | color: #333; 183 | } 184 | .hljs-comment{ 185 | color: #998; 186 | } 187 | .hljs-string{ 188 | color: #d14; 189 | } 190 | .hljs-number{ 191 | color: teal; 192 | } 193 | .hljs-literal{ 194 | color: teal; 195 | } 196 | } 197 | } 198 | } 199 | } 200 | 201 | // 目录 202 | .catalog-wrap{ 203 | overflow: hidden; 204 | overflow: auto; 205 | height: 100vh; 206 | max-height: 100vh; 207 | // margin: 15px 15px 15px 0; 208 | // border-left: 1px solid #eee; 209 | box-shadow: 0 1px 1px #ccc; 210 | background-color: #fff; 211 | h3{ 212 | padding: 5px 20px 5px 20px; 213 | } 214 | &::-webkit-scrollbar{ 215 | width: 1px; 216 | } 217 | ul{ 218 | list-style-type: none; 219 | .active{ 220 | padding: 5px 20px 5px 20px; 221 | background-color: #f3f3f3; 222 | border-left: 3px solid #3db8c1; 223 | a{ 224 | color: #3db8c1; 225 | } 226 | } 227 | li{ 228 | padding: 5px 20px 5px 20px; 229 | a{ 230 | text-decoration: none; 231 | color: #222222; 232 | cursor: pointer; 233 | } 234 | } 235 | .h3{ 236 | padding-left: 40px; 237 | } 238 | .h4{ 239 | padding-left: 80px; 240 | } 241 | } 242 | } 243 | 244 | // 删除提示 245 | .delect-wrap{ 246 | position: fixed; 247 | top: 0; 248 | left: 0; 249 | width: 100vw; 250 | height: 100vh; 251 | opacity: .97; 252 | background-color: #fff; 253 | display: flex; 254 | align-items: center; 255 | .delect-content{ 256 | width: 800px; 257 | height: 310px; 258 | margin: 0 auto; 259 | .icon{ 260 | text-align: center; 261 | height: 42px; 262 | line-height: 42px; 263 | font-size: 36px; 264 | margin-bottom: 10px; 265 | color: #ababab; 266 | } 267 | .text{ 268 | text-align: center; 269 | margin: 0 40%; 270 | height: 50px; 271 | line-height: 50px; 272 | border-bottom: 1px #d9d9d9 solid; 273 | } 274 | .tip{ 275 | height: 120px; 276 | line-height: 120px; 277 | font-size: 20px; 278 | text-align: center; 279 | color: #383838; 280 | em{ 281 | font-weight: bold; 282 | } 283 | } 284 | .btn{ 285 | text-align: center; 286 | button{ 287 | color: #606060; 288 | background-color: #ececec; 289 | border: 1px solid #d9d9d9; 290 | cursor: pointer; 291 | border-radius: 5px; 292 | margin: 0 4px; 293 | padding: 9px 71px; 294 | font-size: 13px; 295 | outline: none; 296 | } 297 | .delete{ 298 | background-color: #3db8c1; 299 | color: #fff; 300 | border: none; 301 | } 302 | } 303 | } 304 | } -------------------------------------------------------------------------------- /src/app/page/view-note/view-note.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ViewNoteComponent } from './view-note.component'; 4 | 5 | describe('ViewNoteComponent', () => { 6 | let component: ViewNoteComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ ViewNoteComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ViewNoteComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should be created', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/page/view-note/view-note.component.ts: -------------------------------------------------------------------------------- 1 | import { style } from '@angular/animations'; 2 | import { Router } from '@angular/router'; 3 | import { MsgService } from './../../services/msg/msg.service'; 4 | import { NoteService } from './../../services/note/note.service'; 5 | import { Component, OnInit, ViewEncapsulation, ElementRef, Input } from '@angular/core'; 6 | import marked from 'marked'; 7 | import highlight from 'highlight.js'; 8 | 9 | @Component({ 10 | selector: 'app-view-note', 11 | templateUrl: './view-note.component.html', 12 | styleUrls: ['./view-note.component.scss'], 13 | encapsulation: ViewEncapsulation.None 14 | }) 15 | export class ViewNoteComponent implements OnInit { 16 | isShowEdit = false; 17 | noteInfo = { 18 | content: '', 19 | date: String, 20 | sourceLink: '', 21 | tag: Array, 22 | title: String, 23 | __v: Number, 24 | _id: String 25 | }; 26 | isShowDelect = false; 27 | catalog = []; 28 | 29 | constructor( 30 | private noteService: NoteService, 31 | private msgService: MsgService, 32 | private router: Router 33 | ) { } 34 | 35 | ngOnInit() { 36 | this.noteInfo = JSON.parse(localStorage.getItem('noteItemInfo')); 37 | this.noteInfo.sourceLink === '' ? this.isShowEdit = true : this.isShowEdit = false; 38 | this.noteInfo.content = marked(this.noteInfo.content, { 39 | renderer: new marked.Renderer(), 40 | gfm: true, 41 | pedantic: false, 42 | sanitize: false, 43 | tables: true, 44 | breaks: true, 45 | smartLists: true, 46 | smartypants: true, 47 | highlight: function (code) { 48 | return highlight.highlightAuto(code).value; 49 | } 50 | }); 51 | 52 | const contentDom = this.parseDom(this.noteInfo.content); // 把html字符串装换成DOM 53 | // 提取笔记内容生成目录信息 54 | Array.prototype.slice.call(contentDom.querySelectorAll('h1,h2,h3,h4,h5,h6')).forEach((item, index) => { 55 | item.id = item.localName + '-' + index; 56 | let active; 57 | index === 0 ? active = true : active = false; 58 | this.catalog.push({ 59 | tagName: item.localName, 60 | text: item.innerText, 61 | href: '#' + item.localName + '-' + index, 62 | el: item, 63 | isActive: active 64 | }); 65 | }); 66 | const previewDom = document.querySelector('.preview'); 67 | const catalogDom = document.querySelector('.catalog-wrap'); 68 | previewDom.appendChild(contentDom); 69 | if (this.catalog.length === 0) { 70 | return; 71 | } 72 | previewDom.addEventListener('scroll', this.throttle(() => { 73 | this.catalog.forEach((item, index) => { 74 | if (index !== this.catalog.length - 1) { 75 | if ((previewDom.scrollTop + 125) > item.el.offsetTop && (previewDom.scrollTop + 125) < this.catalog[index + 1].el.offsetTop) { 76 | this.catalog.forEach(j => j.isActive = false); 77 | item.isActive = true; 78 | } 79 | }else { 80 | if ((previewDom.scrollTop + 125) > item.el.offsetTop) { 81 | this.catalog.forEach(j => j.isActive = false); 82 | item.isActive = true; 83 | } 84 | } 85 | }); 86 | const activeDom = catalogDom.querySelector('.active'); 87 | catalogDom.scrollTop = activeDom.offsetTop - window.innerHeight / 2; 88 | }, 200)); 89 | 90 | } 91 | 92 | delect() { 93 | this.isShowDelect = true; 94 | } 95 | 96 | cancel() { 97 | this.isShowDelect = false; 98 | } 99 | 100 | confirmDelete() { 101 | const id = this.noteInfo._id; 102 | this.noteService._deleteNote(id).subscribe((res) => { 103 | if (res['data'].ok === 1) { 104 | this.isShowDelect = false; 105 | this.msgService.info('删除成功!'); 106 | this.noteService._updateAllNote(); 107 | this.router.navigate(['/classification']); 108 | } 109 | }); 110 | } 111 | 112 | editNote() { 113 | this.router.navigate(['/editNote']); 114 | } 115 | 116 | catalogNavigation(data) { 117 | const previewDom = document.querySelector('.preview'); 118 | const activeNode = previewDom.querySelector(`${data.href}`); 119 | previewDom.scrollTop = activeNode.offsetTop - 120; 120 | } 121 | 122 | private parseDom(arg) { 123 | const objl = document.createElement('div'); 124 | objl.innerHTML = arg; 125 | return objl; 126 | } 127 | 128 | private throttle(fn, interval = 300) { 129 | let canRun = true; 130 | return function () { 131 | if (!canRun) { 132 | return; 133 | } 134 | canRun = false; 135 | setTimeout(function() { 136 | fn.apply(this, arguments); 137 | canRun = true; 138 | }, interval); 139 | }; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/app/services/loading-bar/loading-bar.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { LoadingBarService } from './loading-bar.service'; 4 | 5 | describe('LoadingBarService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [LoadingBarService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([LoadingBarService], (service: LoadingBarService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/services/loading-bar/loading-bar.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class LoadingBarService { 5 | constructor() { } 6 | 7 | public $Loading = { 8 | start: function(){ 9 | const myLoadingBar = document.querySelector('.my-loading-bar'); 10 | if (myLoadingBar !== null && myLoadingBar instanceof HTMLElement) { 11 | let LoadingBarDivWidth = 0; 12 | this.timer = setInterval(() => { 13 | LoadingBarDivWidth++; 14 | myLoadingBar.style.width = LoadingBarDivWidth + 'vw'; 15 | if (LoadingBarDivWidth >= 100) { 16 | clearInterval(this.timer); 17 | } 18 | }, 25); 19 | } else { 20 | const LoadingBarDiv = document.createElement('div'); 21 | LoadingBarDiv.className = 'my-loading-bar'; 22 | const bodyEl = document.querySelector('body'); 23 | bodyEl.appendChild(LoadingBarDiv); 24 | let LoadingBarDivWidth = 0; 25 | this.timer = setInterval(() => { 26 | LoadingBarDivWidth++; 27 | LoadingBarDiv.style.width = LoadingBarDivWidth + 'vw'; 28 | if (LoadingBarDivWidth >= 100) { 29 | clearInterval(this.timer); 30 | } 31 | }, 25); 32 | } 33 | }, 34 | finish: function(){ 35 | const myLoadingBar = document.querySelector('.my-loading-bar'); 36 | if (myLoadingBar !== null && myLoadingBar instanceof HTMLElement) { 37 | clearInterval(this.timer); 38 | myLoadingBar.style.width = 0 + 'vw'; 39 | } 40 | } 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /src/app/services/msg/msg.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { MsgService } from './msg.service'; 4 | 5 | describe('MsgService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [MsgService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([MsgService], (service: MsgService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/services/msg/msg.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class MsgService { 5 | 6 | private createTpl(msg: String) { 7 | const tpl = ` 8 |
9 | 10 | ${msg} 11 |
12 | `; 13 | return tpl; 14 | } 15 | 16 | constructor() { } 17 | 18 | info(msg: String) { 19 | const msgWrapEl = document.querySelector('.msg-wrap'); 20 | if (!msgWrapEl) { 21 | const bodyEl = document.querySelector('body'); 22 | bodyEl.appendChild(this.parseDom(this.createTpl(msg))); 23 | const msgWrapEl = document.querySelector('.msg-wrap'); 24 | setTimeout(() => { 25 | bodyEl.removeChild(msgWrapEl); 26 | }, 2000); 27 | } 28 | } 29 | 30 | private parseDom(arg) { 31 | const objl = document.createElement('div'); 32 | objl.className = 'msg-wrap'; 33 | objl.innerHTML = arg; 34 | return objl; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/services/note/note.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { NoteService } from './note.service'; 4 | 5 | describe('NoteService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [NoteService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([NoteService], (service: NoteService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/services/note/note.service.ts: -------------------------------------------------------------------------------- 1 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 2 | import { Injectable } from '@angular/core'; 3 | import { HttpClient } from '@angular/common/http'; 4 | 5 | @Injectable() 6 | export class NoteService { 7 | allNote = []; 8 | allNote$ = new BehaviorSubject>(this.allNote); 9 | 10 | constructor( 11 | private http: HttpClient 12 | ) { 13 | this._updateAllNote(); 14 | } 15 | 16 | // 添加 17 | _addNote(param: editNote) { 18 | return this.http.post('/api/addNote', param); 19 | } 20 | 21 | // 修改 22 | _modifyNote(param: editNote) { 23 | return this.http.post('/api/modify', param); 24 | } 25 | 26 | // 删除 27 | _deleteNote(id) { 28 | return this.http.post('/api/deleteNote', {id: id}); 29 | } 30 | 31 | _updateAllNote() { 32 | this.http.get('/api/allNote') 33 | .subscribe((data) => { 34 | this.allNote$.next(data['data']); 35 | }); 36 | } 37 | } 38 | 39 | interface editNote { 40 | title: String; 41 | content: String; 42 | tag: Array; 43 | date: Date; 44 | sourceLink: String; 45 | _id?: String; 46 | } 47 | -------------------------------------------------------------------------------- /src/app/services/tag/tag.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, inject } from '@angular/core/testing'; 2 | 3 | import { DropdownMenuListService } from './dropdown-menu-list.service'; 4 | 5 | describe('DropdownMenuListService', () => { 6 | beforeEach(() => { 7 | TestBed.configureTestingModule({ 8 | providers: [DropdownMenuListService] 9 | }); 10 | }); 11 | 12 | it('should be created', inject([DropdownMenuListService], (service: DropdownMenuListService) => { 13 | expect(service).toBeTruthy(); 14 | })); 15 | }); 16 | -------------------------------------------------------------------------------- /src/app/services/tag/tag.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 4 | 5 | @Injectable() 6 | export class TagService { 7 | tagList = []; 8 | tagList$ = new BehaviorSubject>(this.tagList); 9 | 10 | constructor( 11 | private http: HttpClient 12 | ) { 13 | this._updateTagList(); 14 | } 15 | 16 | // 新增 17 | _addTag(name: String) { 18 | this.http.post('/api/addTag', { 19 | name: name 20 | }) 21 | .subscribe((data) => { 22 | this._updateTagList(); 23 | }); 24 | } 25 | 26 | // 删除 27 | _deleteTag(id) { 28 | this.http.post('/api/deleteTag', { 29 | id: id 30 | }).subscribe((data) => { 31 | this._updateTagList(); 32 | }); 33 | } 34 | 35 | // 获取整个列表 36 | _getTagList() { 37 | return this.http.get('/api/TagList'); 38 | } 39 | 40 | _updateTagList() { 41 | this.http.get('/api/TagList') 42 | .subscribe((res) => { 43 | this.tagList$.next(res['data']); 44 | }); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linguowei/micro-note/bbca53bc7f164966434107823ee568f443e370e4/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/fonts/ionicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linguowei/micro-note/bbca53bc7f164966434107823ee568f443e370e4/src/assets/fonts/ionicons.eot -------------------------------------------------------------------------------- /src/assets/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linguowei/micro-note/bbca53bc7f164966434107823ee568f443e370e4/src/assets/fonts/ionicons.ttf -------------------------------------------------------------------------------- /src/assets/fonts/ionicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linguowei/micro-note/bbca53bc7f164966434107823ee568f443e370e4/src/assets/fonts/ionicons.woff -------------------------------------------------------------------------------- /src/assets/index.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/ionicons.min.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8";/*! 2 | Ionicons, v2.0.0 3 | Created by Ben Sperry for the Ionic Framework, http://ionicons.com/ 4 | https://twitter.com/benjsperry https://twitter.com/ionicframework 5 | MIT License: https://github.com/driftyco/ionicons 6 | 7 | Android-style icons originally built by Google’s 8 | Material Design Icons: https://github.com/google/material-design-icons 9 | used under CC BY http://creativecommons.org/licenses/by/4.0/ 10 | Modified icons to fit ionicon’s grid from original. 11 | */@font-face{font-family:"Ionicons";src:url("./fonts/ionicons.eot?v=2.0.0");src:url("./fonts/ionicons.eot?v=2.0.0#iefix") format("embedded-opentype"),url("./fonts/ionicons.ttf?v=2.0.0") format("truetype"),url("./fonts/ionicons.woff?v=2.0.0") format("woff"),url("./fonts/ionicons.svg?v=2.0.0#Ionicons") format("svg");font-weight:normal;font-style:normal}.ion,.ionicons,.ion-alert:before,.ion-alert-circled:before,.ion-android-add:before,.ion-android-add-circle:before,.ion-android-alarm-clock:before,.ion-android-alert:before,.ion-android-apps:before,.ion-android-archive:before,.ion-android-arrow-back:before,.ion-android-arrow-down:before,.ion-android-arrow-dropdown:before,.ion-android-arrow-dropdown-circle:before,.ion-android-arrow-dropleft:before,.ion-android-arrow-dropleft-circle:before,.ion-android-arrow-dropright:before,.ion-android-arrow-dropright-circle:before,.ion-android-arrow-dropup:before,.ion-android-arrow-dropup-circle:before,.ion-android-arrow-forward:before,.ion-android-arrow-up:before,.ion-android-attach:before,.ion-android-bar:before,.ion-android-bicycle:before,.ion-android-boat:before,.ion-android-bookmark:before,.ion-android-bulb:before,.ion-android-bus:before,.ion-android-calendar:before,.ion-android-call:before,.ion-android-camera:before,.ion-android-cancel:before,.ion-android-car:before,.ion-android-cart:before,.ion-android-chat:before,.ion-android-checkbox:before,.ion-android-checkbox-blank:before,.ion-android-checkbox-outline:before,.ion-android-checkbox-outline-blank:before,.ion-android-checkmark-circle:before,.ion-android-clipboard:before,.ion-android-close:before,.ion-android-cloud:before,.ion-android-cloud-circle:before,.ion-android-cloud-done:before,.ion-android-cloud-outline:before,.ion-android-color-palette:before,.ion-android-compass:before,.ion-android-contact:before,.ion-android-contacts:before,.ion-android-contract:before,.ion-android-create:before,.ion-android-delete:before,.ion-android-desktop:before,.ion-android-document:before,.ion-android-done:before,.ion-android-done-all:before,.ion-android-download:before,.ion-android-drafts:before,.ion-android-exit:before,.ion-android-expand:before,.ion-android-favorite:before,.ion-android-favorite-outline:before,.ion-android-film:before,.ion-android-folder:before,.ion-android-folder-open:before,.ion-android-funnel:before,.ion-android-globe:before,.ion-android-hand:before,.ion-android-hangout:before,.ion-android-happy:before,.ion-android-home:before,.ion-android-image:before,.ion-android-laptop:before,.ion-android-list:before,.ion-android-locate:before,.ion-android-lock:before,.ion-android-mail:before,.ion-android-map:before,.ion-android-menu:before,.ion-android-microphone:before,.ion-android-microphone-off:before,.ion-android-more-horizontal:before,.ion-android-more-vertical:before,.ion-android-navigate:before,.ion-android-notifications:before,.ion-android-notifications-none:before,.ion-android-notifications-off:before,.ion-android-open:before,.ion-android-options:before,.ion-android-people:before,.ion-android-person:before,.ion-android-person-add:before,.ion-android-phone-landscape:before,.ion-android-phone-portrait:before,.ion-android-pin:before,.ion-android-plane:before,.ion-android-playstore:before,.ion-android-print:before,.ion-android-radio-button-off:before,.ion-android-radio-button-on:before,.ion-android-refresh:before,.ion-android-remove:before,.ion-android-remove-circle:before,.ion-android-restaurant:before,.ion-android-sad:before,.ion-android-search:before,.ion-android-send:before,.ion-android-settings:before,.ion-android-share:before,.ion-android-share-alt:before,.ion-android-star:before,.ion-android-star-half:before,.ion-android-star-outline:before,.ion-android-stopwatch:before,.ion-android-subway:before,.ion-android-sunny:before,.ion-android-sync:before,.ion-android-textsms:before,.ion-android-time:before,.ion-android-train:before,.ion-android-unlock:before,.ion-android-upload:before,.ion-android-volume-down:before,.ion-android-volume-mute:before,.ion-android-volume-off:before,.ion-android-volume-up:before,.ion-android-walk:before,.ion-android-warning:before,.ion-android-watch:before,.ion-android-wifi:before,.ion-aperture:before,.ion-archive:before,.ion-arrow-down-a:before,.ion-arrow-down-b:before,.ion-arrow-down-c:before,.ion-arrow-expand:before,.ion-arrow-graph-down-left:before,.ion-arrow-graph-down-right:before,.ion-arrow-graph-up-left:before,.ion-arrow-graph-up-right:before,.ion-arrow-left-a:before,.ion-arrow-left-b:before,.ion-arrow-left-c:before,.ion-arrow-move:before,.ion-arrow-resize:before,.ion-arrow-return-left:before,.ion-arrow-return-right:before,.ion-arrow-right-a:before,.ion-arrow-right-b:before,.ion-arrow-right-c:before,.ion-arrow-shrink:before,.ion-arrow-swap:before,.ion-arrow-up-a:before,.ion-arrow-up-b:before,.ion-arrow-up-c:before,.ion-asterisk:before,.ion-at:before,.ion-backspace:before,.ion-backspace-outline:before,.ion-bag:before,.ion-battery-charging:before,.ion-battery-empty:before,.ion-battery-full:before,.ion-battery-half:before,.ion-battery-low:before,.ion-beaker:before,.ion-beer:before,.ion-bluetooth:before,.ion-bonfire:before,.ion-bookmark:before,.ion-bowtie:before,.ion-briefcase:before,.ion-bug:before,.ion-calculator:before,.ion-calendar:before,.ion-camera:before,.ion-card:before,.ion-cash:before,.ion-chatbox:before,.ion-chatbox-working:before,.ion-chatboxes:before,.ion-chatbubble:before,.ion-chatbubble-working:before,.ion-chatbubbles:before,.ion-checkmark:before,.ion-checkmark-circled:before,.ion-checkmark-round:before,.ion-chevron-down:before,.ion-chevron-left:before,.ion-chevron-right:before,.ion-chevron-up:before,.ion-clipboard:before,.ion-clock:before,.ion-close:before,.ion-close-circled:before,.ion-close-round:before,.ion-closed-captioning:before,.ion-cloud:before,.ion-code:before,.ion-code-download:before,.ion-code-working:before,.ion-coffee:before,.ion-compass:before,.ion-compose:before,.ion-connection-bars:before,.ion-contrast:before,.ion-crop:before,.ion-cube:before,.ion-disc:before,.ion-document:before,.ion-document-text:before,.ion-drag:before,.ion-earth:before,.ion-easel:before,.ion-edit:before,.ion-egg:before,.ion-eject:before,.ion-email:before,.ion-email-unread:before,.ion-erlenmeyer-flask:before,.ion-erlenmeyer-flask-bubbles:before,.ion-eye:before,.ion-eye-disabled:before,.ion-female:before,.ion-filing:before,.ion-film-marker:before,.ion-fireball:before,.ion-flag:before,.ion-flame:before,.ion-flash:before,.ion-flash-off:before,.ion-folder:before,.ion-fork:before,.ion-fork-repo:before,.ion-forward:before,.ion-funnel:before,.ion-gear-a:before,.ion-gear-b:before,.ion-grid:before,.ion-hammer:before,.ion-happy:before,.ion-happy-outline:before,.ion-headphone:before,.ion-heart:before,.ion-heart-broken:before,.ion-help:before,.ion-help-buoy:before,.ion-help-circled:before,.ion-home:before,.ion-icecream:before,.ion-image:before,.ion-images:before,.ion-information:before,.ion-information-circled:before,.ion-ionic:before,.ion-ios-alarm:before,.ion-ios-alarm-outline:before,.ion-ios-albums:before,.ion-ios-albums-outline:before,.ion-ios-americanfootball:before,.ion-ios-americanfootball-outline:before,.ion-ios-analytics:before,.ion-ios-analytics-outline:before,.ion-ios-arrow-back:before,.ion-ios-arrow-down:before,.ion-ios-arrow-forward:before,.ion-ios-arrow-left:before,.ion-ios-arrow-right:before,.ion-ios-arrow-thin-down:before,.ion-ios-arrow-thin-left:before,.ion-ios-arrow-thin-right:before,.ion-ios-arrow-thin-up:before,.ion-ios-arrow-up:before,.ion-ios-at:before,.ion-ios-at-outline:before,.ion-ios-barcode:before,.ion-ios-barcode-outline:before,.ion-ios-baseball:before,.ion-ios-baseball-outline:before,.ion-ios-basketball:before,.ion-ios-basketball-outline:before,.ion-ios-bell:before,.ion-ios-bell-outline:before,.ion-ios-body:before,.ion-ios-body-outline:before,.ion-ios-bolt:before,.ion-ios-bolt-outline:before,.ion-ios-book:before,.ion-ios-book-outline:before,.ion-ios-bookmarks:before,.ion-ios-bookmarks-outline:before,.ion-ios-box:before,.ion-ios-box-outline:before,.ion-ios-briefcase:before,.ion-ios-briefcase-outline:before,.ion-ios-browsers:before,.ion-ios-browsers-outline:before,.ion-ios-calculator:before,.ion-ios-calculator-outline:before,.ion-ios-calendar:before,.ion-ios-calendar-outline:before,.ion-ios-camera:before,.ion-ios-camera-outline:before,.ion-ios-cart:before,.ion-ios-cart-outline:before,.ion-ios-chatboxes:before,.ion-ios-chatboxes-outline:before,.ion-ios-chatbubble:before,.ion-ios-chatbubble-outline:before,.ion-ios-checkmark:before,.ion-ios-checkmark-empty:before,.ion-ios-checkmark-outline:before,.ion-ios-circle-filled:before,.ion-ios-circle-outline:before,.ion-ios-clock:before,.ion-ios-clock-outline:before,.ion-ios-close:before,.ion-ios-close-empty:before,.ion-ios-close-outline:before,.ion-ios-cloud:before,.ion-ios-cloud-download:before,.ion-ios-cloud-download-outline:before,.ion-ios-cloud-outline:before,.ion-ios-cloud-upload:before,.ion-ios-cloud-upload-outline:before,.ion-ios-cloudy:before,.ion-ios-cloudy-night:before,.ion-ios-cloudy-night-outline:before,.ion-ios-cloudy-outline:before,.ion-ios-cog:before,.ion-ios-cog-outline:before,.ion-ios-color-filter:before,.ion-ios-color-filter-outline:before,.ion-ios-color-wand:before,.ion-ios-color-wand-outline:before,.ion-ios-compose:before,.ion-ios-compose-outline:before,.ion-ios-contact:before,.ion-ios-contact-outline:before,.ion-ios-copy:before,.ion-ios-copy-outline:before,.ion-ios-crop:before,.ion-ios-crop-strong:before,.ion-ios-download:before,.ion-ios-download-outline:before,.ion-ios-drag:before,.ion-ios-email:before,.ion-ios-email-outline:before,.ion-ios-eye:before,.ion-ios-eye-outline:before,.ion-ios-fastforward:before,.ion-ios-fastforward-outline:before,.ion-ios-filing:before,.ion-ios-filing-outline:before,.ion-ios-film:before,.ion-ios-film-outline:before,.ion-ios-flag:before,.ion-ios-flag-outline:before,.ion-ios-flame:before,.ion-ios-flame-outline:before,.ion-ios-flask:before,.ion-ios-flask-outline:before,.ion-ios-flower:before,.ion-ios-flower-outline:before,.ion-ios-folder:before,.ion-ios-folder-outline:before,.ion-ios-football:before,.ion-ios-football-outline:before,.ion-ios-game-controller-a:before,.ion-ios-game-controller-a-outline:before,.ion-ios-game-controller-b:before,.ion-ios-game-controller-b-outline:before,.ion-ios-gear:before,.ion-ios-gear-outline:before,.ion-ios-glasses:before,.ion-ios-glasses-outline:before,.ion-ios-grid-view:before,.ion-ios-grid-view-outline:before,.ion-ios-heart:before,.ion-ios-heart-outline:before,.ion-ios-help:before,.ion-ios-help-empty:before,.ion-ios-help-outline:before,.ion-ios-home:before,.ion-ios-home-outline:before,.ion-ios-infinite:before,.ion-ios-infinite-outline:before,.ion-ios-information:before,.ion-ios-information-empty:before,.ion-ios-information-outline:before,.ion-ios-ionic-outline:before,.ion-ios-keypad:before,.ion-ios-keypad-outline:before,.ion-ios-lightbulb:before,.ion-ios-lightbulb-outline:before,.ion-ios-list:before,.ion-ios-list-outline:before,.ion-ios-location:before,.ion-ios-location-outline:before,.ion-ios-locked:before,.ion-ios-locked-outline:before,.ion-ios-loop:before,.ion-ios-loop-strong:before,.ion-ios-medical:before,.ion-ios-medical-outline:before,.ion-ios-medkit:before,.ion-ios-medkit-outline:before,.ion-ios-mic:before,.ion-ios-mic-off:before,.ion-ios-mic-outline:before,.ion-ios-minus:before,.ion-ios-minus-empty:before,.ion-ios-minus-outline:before,.ion-ios-monitor:before,.ion-ios-monitor-outline:before,.ion-ios-moon:before,.ion-ios-moon-outline:before,.ion-ios-more:before,.ion-ios-more-outline:before,.ion-ios-musical-note:before,.ion-ios-musical-notes:before,.ion-ios-navigate:before,.ion-ios-navigate-outline:before,.ion-ios-nutrition:before,.ion-ios-nutrition-outline:before,.ion-ios-paper:before,.ion-ios-paper-outline:before,.ion-ios-paperplane:before,.ion-ios-paperplane-outline:before,.ion-ios-partlysunny:before,.ion-ios-partlysunny-outline:before,.ion-ios-pause:before,.ion-ios-pause-outline:before,.ion-ios-paw:before,.ion-ios-paw-outline:before,.ion-ios-people:before,.ion-ios-people-outline:before,.ion-ios-person:before,.ion-ios-person-outline:before,.ion-ios-personadd:before,.ion-ios-personadd-outline:before,.ion-ios-photos:before,.ion-ios-photos-outline:before,.ion-ios-pie:before,.ion-ios-pie-outline:before,.ion-ios-pint:before,.ion-ios-pint-outline:before,.ion-ios-play:before,.ion-ios-play-outline:before,.ion-ios-plus:before,.ion-ios-plus-empty:before,.ion-ios-plus-outline:before,.ion-ios-pricetag:before,.ion-ios-pricetag-outline:before,.ion-ios-pricetags:before,.ion-ios-pricetags-outline:before,.ion-ios-printer:before,.ion-ios-printer-outline:before,.ion-ios-pulse:before,.ion-ios-pulse-strong:before,.ion-ios-rainy:before,.ion-ios-rainy-outline:before,.ion-ios-recording:before,.ion-ios-recording-outline:before,.ion-ios-redo:before,.ion-ios-redo-outline:before,.ion-ios-refresh:before,.ion-ios-refresh-empty:before,.ion-ios-refresh-outline:before,.ion-ios-reload:before,.ion-ios-reverse-camera:before,.ion-ios-reverse-camera-outline:before,.ion-ios-rewind:before,.ion-ios-rewind-outline:before,.ion-ios-rose:before,.ion-ios-rose-outline:before,.ion-ios-search:before,.ion-ios-search-strong:before,.ion-ios-settings:before,.ion-ios-settings-strong:before,.ion-ios-shuffle:before,.ion-ios-shuffle-strong:before,.ion-ios-skipbackward:before,.ion-ios-skipbackward-outline:before,.ion-ios-skipforward:before,.ion-ios-skipforward-outline:before,.ion-ios-snowy:before,.ion-ios-speedometer:before,.ion-ios-speedometer-outline:before,.ion-ios-star:before,.ion-ios-star-half:before,.ion-ios-star-outline:before,.ion-ios-stopwatch:before,.ion-ios-stopwatch-outline:before,.ion-ios-sunny:before,.ion-ios-sunny-outline:before,.ion-ios-telephone:before,.ion-ios-telephone-outline:before,.ion-ios-tennisball:before,.ion-ios-tennisball-outline:before,.ion-ios-thunderstorm:before,.ion-ios-thunderstorm-outline:before,.ion-ios-time:before,.ion-ios-time-outline:before,.ion-ios-timer:before,.ion-ios-timer-outline:before,.ion-ios-toggle:before,.ion-ios-toggle-outline:before,.ion-ios-trash:before,.ion-ios-trash-outline:before,.ion-ios-undo:before,.ion-ios-undo-outline:before,.ion-ios-unlocked:before,.ion-ios-unlocked-outline:before,.ion-ios-upload:before,.ion-ios-upload-outline:before,.ion-ios-videocam:before,.ion-ios-videocam-outline:before,.ion-ios-volume-high:before,.ion-ios-volume-low:before,.ion-ios-wineglass:before,.ion-ios-wineglass-outline:before,.ion-ios-world:before,.ion-ios-world-outline:before,.ion-ipad:before,.ion-iphone:before,.ion-ipod:before,.ion-jet:before,.ion-key:before,.ion-knife:before,.ion-laptop:before,.ion-leaf:before,.ion-levels:before,.ion-lightbulb:before,.ion-link:before,.ion-load-a:before,.ion-load-b:before,.ion-load-c:before,.ion-load-d:before,.ion-location:before,.ion-lock-combination:before,.ion-locked:before,.ion-log-in:before,.ion-log-out:before,.ion-loop:before,.ion-magnet:before,.ion-male:before,.ion-man:before,.ion-map:before,.ion-medkit:before,.ion-merge:before,.ion-mic-a:before,.ion-mic-b:before,.ion-mic-c:before,.ion-minus:before,.ion-minus-circled:before,.ion-minus-round:before,.ion-model-s:before,.ion-monitor:before,.ion-more:before,.ion-mouse:before,.ion-music-note:before,.ion-navicon:before,.ion-navicon-round:before,.ion-navigate:before,.ion-network:before,.ion-no-smoking:before,.ion-nuclear:before,.ion-outlet:before,.ion-paintbrush:before,.ion-paintbucket:before,.ion-paper-airplane:before,.ion-paperclip:before,.ion-pause:before,.ion-person:before,.ion-person-add:before,.ion-person-stalker:before,.ion-pie-graph:before,.ion-pin:before,.ion-pinpoint:before,.ion-pizza:before,.ion-plane:before,.ion-planet:before,.ion-play:before,.ion-playstation:before,.ion-plus:before,.ion-plus-circled:before,.ion-plus-round:before,.ion-podium:before,.ion-pound:before,.ion-power:before,.ion-pricetag:before,.ion-pricetags:before,.ion-printer:before,.ion-pull-request:before,.ion-qr-scanner:before,.ion-quote:before,.ion-radio-waves:before,.ion-record:before,.ion-refresh:before,.ion-reply:before,.ion-reply-all:before,.ion-ribbon-a:before,.ion-ribbon-b:before,.ion-sad:before,.ion-sad-outline:before,.ion-scissors:before,.ion-search:before,.ion-settings:before,.ion-share:before,.ion-shuffle:before,.ion-skip-backward:before,.ion-skip-forward:before,.ion-social-android:before,.ion-social-android-outline:before,.ion-social-angular:before,.ion-social-angular-outline:before,.ion-social-apple:before,.ion-social-apple-outline:before,.ion-social-bitcoin:before,.ion-social-bitcoin-outline:before,.ion-social-buffer:before,.ion-social-buffer-outline:before,.ion-social-chrome:before,.ion-social-chrome-outline:before,.ion-social-codepen:before,.ion-social-codepen-outline:before,.ion-social-css3:before,.ion-social-css3-outline:before,.ion-social-designernews:before,.ion-social-designernews-outline:before,.ion-social-dribbble:before,.ion-social-dribbble-outline:before,.ion-social-dropbox:before,.ion-social-dropbox-outline:before,.ion-social-euro:before,.ion-social-euro-outline:before,.ion-social-facebook:before,.ion-social-facebook-outline:before,.ion-social-foursquare:before,.ion-social-foursquare-outline:before,.ion-social-freebsd-devil:before,.ion-social-github:before,.ion-social-github-outline:before,.ion-social-google:before,.ion-social-google-outline:before,.ion-social-googleplus:before,.ion-social-googleplus-outline:before,.ion-social-hackernews:before,.ion-social-hackernews-outline:before,.ion-social-html5:before,.ion-social-html5-outline:before,.ion-social-instagram:before,.ion-social-instagram-outline:before,.ion-social-javascript:before,.ion-social-javascript-outline:before,.ion-social-linkedin:before,.ion-social-linkedin-outline:before,.ion-social-markdown:before,.ion-social-nodejs:before,.ion-social-octocat:before,.ion-social-pinterest:before,.ion-social-pinterest-outline:before,.ion-social-python:before,.ion-social-reddit:before,.ion-social-reddit-outline:before,.ion-social-rss:before,.ion-social-rss-outline:before,.ion-social-sass:before,.ion-social-skype:before,.ion-social-skype-outline:before,.ion-social-snapchat:before,.ion-social-snapchat-outline:before,.ion-social-tumblr:before,.ion-social-tumblr-outline:before,.ion-social-tux:before,.ion-social-twitch:before,.ion-social-twitch-outline:before,.ion-social-twitter:before,.ion-social-twitter-outline:before,.ion-social-usd:before,.ion-social-usd-outline:before,.ion-social-vimeo:before,.ion-social-vimeo-outline:before,.ion-social-whatsapp:before,.ion-social-whatsapp-outline:before,.ion-social-windows:before,.ion-social-windows-outline:before,.ion-social-wordpress:before,.ion-social-wordpress-outline:before,.ion-social-yahoo:before,.ion-social-yahoo-outline:before,.ion-social-yen:before,.ion-social-yen-outline:before,.ion-social-youtube:before,.ion-social-youtube-outline:before,.ion-soup-can:before,.ion-soup-can-outline:before,.ion-speakerphone:before,.ion-speedometer:before,.ion-spoon:before,.ion-star:before,.ion-stats-bars:before,.ion-steam:before,.ion-stop:before,.ion-thermometer:before,.ion-thumbsdown:before,.ion-thumbsup:before,.ion-toggle:before,.ion-toggle-filled:before,.ion-transgender:before,.ion-trash-a:before,.ion-trash-b:before,.ion-trophy:before,.ion-tshirt:before,.ion-tshirt-outline:before,.ion-umbrella:before,.ion-university:before,.ion-unlocked:before,.ion-upload:before,.ion-usb:before,.ion-videocamera:before,.ion-volume-high:before,.ion-volume-low:before,.ion-volume-medium:before,.ion-volume-mute:before,.ion-wand:before,.ion-waterdrop:before,.ion-wifi:before,.ion-wineglass:before,.ion-woman:before,.ion-wrench:before,.ion-xbox:before{display:inline-block;font-family:"Ionicons";speak:none;font-style:normal;font-weight:normal;font-variant:normal;text-transform:none;text-rendering:auto;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.ion-alert:before{content:"\f101"}.ion-alert-circled:before{content:"\f100"}.ion-android-add:before{content:"\f2c7"}.ion-android-add-circle:before{content:"\f359"}.ion-android-alarm-clock:before{content:"\f35a"}.ion-android-alert:before{content:"\f35b"}.ion-android-apps:before{content:"\f35c"}.ion-android-archive:before{content:"\f2c9"}.ion-android-arrow-back:before{content:"\f2ca"}.ion-android-arrow-down:before{content:"\f35d"}.ion-android-arrow-dropdown:before{content:"\f35f"}.ion-android-arrow-dropdown-circle:before{content:"\f35e"}.ion-android-arrow-dropleft:before{content:"\f361"}.ion-android-arrow-dropleft-circle:before{content:"\f360"}.ion-android-arrow-dropright:before{content:"\f363"}.ion-android-arrow-dropright-circle:before{content:"\f362"}.ion-android-arrow-dropup:before{content:"\f365"}.ion-android-arrow-dropup-circle:before{content:"\f364"}.ion-android-arrow-forward:before{content:"\f30f"}.ion-android-arrow-up:before{content:"\f366"}.ion-android-attach:before{content:"\f367"}.ion-android-bar:before{content:"\f368"}.ion-android-bicycle:before{content:"\f369"}.ion-android-boat:before{content:"\f36a"}.ion-android-bookmark:before{content:"\f36b"}.ion-android-bulb:before{content:"\f36c"}.ion-android-bus:before{content:"\f36d"}.ion-android-calendar:before{content:"\f2d1"}.ion-android-call:before{content:"\f2d2"}.ion-android-camera:before{content:"\f2d3"}.ion-android-cancel:before{content:"\f36e"}.ion-android-car:before{content:"\f36f"}.ion-android-cart:before{content:"\f370"}.ion-android-chat:before{content:"\f2d4"}.ion-android-checkbox:before{content:"\f374"}.ion-android-checkbox-blank:before{content:"\f371"}.ion-android-checkbox-outline:before{content:"\f373"}.ion-android-checkbox-outline-blank:before{content:"\f372"}.ion-android-checkmark-circle:before{content:"\f375"}.ion-android-clipboard:before{content:"\f376"}.ion-android-close:before{content:"\f2d7"}.ion-android-cloud:before{content:"\f37a"}.ion-android-cloud-circle:before{content:"\f377"}.ion-android-cloud-done:before{content:"\f378"}.ion-android-cloud-outline:before{content:"\f379"}.ion-android-color-palette:before{content:"\f37b"}.ion-android-compass:before{content:"\f37c"}.ion-android-contact:before{content:"\f2d8"}.ion-android-contacts:before{content:"\f2d9"}.ion-android-contract:before{content:"\f37d"}.ion-android-create:before{content:"\f37e"}.ion-android-delete:before{content:"\f37f"}.ion-android-desktop:before{content:"\f380"}.ion-android-document:before{content:"\f381"}.ion-android-done:before{content:"\f383"}.ion-android-done-all:before{content:"\f382"}.ion-android-download:before{content:"\f2dd"}.ion-android-drafts:before{content:"\f384"}.ion-android-exit:before{content:"\f385"}.ion-android-expand:before{content:"\f386"}.ion-android-favorite:before{content:"\f388"}.ion-android-favorite-outline:before{content:"\f387"}.ion-android-film:before{content:"\f389"}.ion-android-folder:before{content:"\f2e0"}.ion-android-folder-open:before{content:"\f38a"}.ion-android-funnel:before{content:"\f38b"}.ion-android-globe:before{content:"\f38c"}.ion-android-hand:before{content:"\f2e3"}.ion-android-hangout:before{content:"\f38d"}.ion-android-happy:before{content:"\f38e"}.ion-android-home:before{content:"\f38f"}.ion-android-image:before{content:"\f2e4"}.ion-android-laptop:before{content:"\f390"}.ion-android-list:before{content:"\f391"}.ion-android-locate:before{content:"\f2e9"}.ion-android-lock:before{content:"\f392"}.ion-android-mail:before{content:"\f2eb"}.ion-android-map:before{content:"\f393"}.ion-android-menu:before{content:"\f394"}.ion-android-microphone:before{content:"\f2ec"}.ion-android-microphone-off:before{content:"\f395"}.ion-android-more-horizontal:before{content:"\f396"}.ion-android-more-vertical:before{content:"\f397"}.ion-android-navigate:before{content:"\f398"}.ion-android-notifications:before{content:"\f39b"}.ion-android-notifications-none:before{content:"\f399"}.ion-android-notifications-off:before{content:"\f39a"}.ion-android-open:before{content:"\f39c"}.ion-android-options:before{content:"\f39d"}.ion-android-people:before{content:"\f39e"}.ion-android-person:before{content:"\f3a0"}.ion-android-person-add:before{content:"\f39f"}.ion-android-phone-landscape:before{content:"\f3a1"}.ion-android-phone-portrait:before{content:"\f3a2"}.ion-android-pin:before{content:"\f3a3"}.ion-android-plane:before{content:"\f3a4"}.ion-android-playstore:before{content:"\f2f0"}.ion-android-print:before{content:"\f3a5"}.ion-android-radio-button-off:before{content:"\f3a6"}.ion-android-radio-button-on:before{content:"\f3a7"}.ion-android-refresh:before{content:"\f3a8"}.ion-android-remove:before{content:"\f2f4"}.ion-android-remove-circle:before{content:"\f3a9"}.ion-android-restaurant:before{content:"\f3aa"}.ion-android-sad:before{content:"\f3ab"}.ion-android-search:before{content:"\f2f5"}.ion-android-send:before{content:"\f2f6"}.ion-android-settings:before{content:"\f2f7"}.ion-android-share:before{content:"\f2f8"}.ion-android-share-alt:before{content:"\f3ac"}.ion-android-star:before{content:"\f2fc"}.ion-android-star-half:before{content:"\f3ad"}.ion-android-star-outline:before{content:"\f3ae"}.ion-android-stopwatch:before{content:"\f2fd"}.ion-android-subway:before{content:"\f3af"}.ion-android-sunny:before{content:"\f3b0"}.ion-android-sync:before{content:"\f3b1"}.ion-android-textsms:before{content:"\f3b2"}.ion-android-time:before{content:"\f3b3"}.ion-android-train:before{content:"\f3b4"}.ion-android-unlock:before{content:"\f3b5"}.ion-android-upload:before{content:"\f3b6"}.ion-android-volume-down:before{content:"\f3b7"}.ion-android-volume-mute:before{content:"\f3b8"}.ion-android-volume-off:before{content:"\f3b9"}.ion-android-volume-up:before{content:"\f3ba"}.ion-android-walk:before{content:"\f3bb"}.ion-android-warning:before{content:"\f3bc"}.ion-android-watch:before{content:"\f3bd"}.ion-android-wifi:before{content:"\f305"}.ion-aperture:before{content:"\f313"}.ion-archive:before{content:"\f102"}.ion-arrow-down-a:before{content:"\f103"}.ion-arrow-down-b:before{content:"\f104"}.ion-arrow-down-c:before{content:"\f105"}.ion-arrow-expand:before{content:"\f25e"}.ion-arrow-graph-down-left:before{content:"\f25f"}.ion-arrow-graph-down-right:before{content:"\f260"}.ion-arrow-graph-up-left:before{content:"\f261"}.ion-arrow-graph-up-right:before{content:"\f262"}.ion-arrow-left-a:before{content:"\f106"}.ion-arrow-left-b:before{content:"\f107"}.ion-arrow-left-c:before{content:"\f108"}.ion-arrow-move:before{content:"\f263"}.ion-arrow-resize:before{content:"\f264"}.ion-arrow-return-left:before{content:"\f265"}.ion-arrow-return-right:before{content:"\f266"}.ion-arrow-right-a:before{content:"\f109"}.ion-arrow-right-b:before{content:"\f10a"}.ion-arrow-right-c:before{content:"\f10b"}.ion-arrow-shrink:before{content:"\f267"}.ion-arrow-swap:before{content:"\f268"}.ion-arrow-up-a:before{content:"\f10c"}.ion-arrow-up-b:before{content:"\f10d"}.ion-arrow-up-c:before{content:"\f10e"}.ion-asterisk:before{content:"\f314"}.ion-at:before{content:"\f10f"}.ion-backspace:before{content:"\f3bf"}.ion-backspace-outline:before{content:"\f3be"}.ion-bag:before{content:"\f110"}.ion-battery-charging:before{content:"\f111"}.ion-battery-empty:before{content:"\f112"}.ion-battery-full:before{content:"\f113"}.ion-battery-half:before{content:"\f114"}.ion-battery-low:before{content:"\f115"}.ion-beaker:before{content:"\f269"}.ion-beer:before{content:"\f26a"}.ion-bluetooth:before{content:"\f116"}.ion-bonfire:before{content:"\f315"}.ion-bookmark:before{content:"\f26b"}.ion-bowtie:before{content:"\f3c0"}.ion-briefcase:before{content:"\f26c"}.ion-bug:before{content:"\f2be"}.ion-calculator:before{content:"\f26d"}.ion-calendar:before{content:"\f117"}.ion-camera:before{content:"\f118"}.ion-card:before{content:"\f119"}.ion-cash:before{content:"\f316"}.ion-chatbox:before{content:"\f11b"}.ion-chatbox-working:before{content:"\f11a"}.ion-chatboxes:before{content:"\f11c"}.ion-chatbubble:before{content:"\f11e"}.ion-chatbubble-working:before{content:"\f11d"}.ion-chatbubbles:before{content:"\f11f"}.ion-checkmark:before{content:"\f122"}.ion-checkmark-circled:before{content:"\f120"}.ion-checkmark-round:before{content:"\f121"}.ion-chevron-down:before{content:"\f123"}.ion-chevron-left:before{content:"\f124"}.ion-chevron-right:before{content:"\f125"}.ion-chevron-up:before{content:"\f126"}.ion-clipboard:before{content:"\f127"}.ion-clock:before{content:"\f26e"}.ion-close:before{content:"\f12a"}.ion-close-circled:before{content:"\f128"}.ion-close-round:before{content:"\f129"}.ion-closed-captioning:before{content:"\f317"}.ion-cloud:before{content:"\f12b"}.ion-code:before{content:"\f271"}.ion-code-download:before{content:"\f26f"}.ion-code-working:before{content:"\f270"}.ion-coffee:before{content:"\f272"}.ion-compass:before{content:"\f273"}.ion-compose:before{content:"\f12c"}.ion-connection-bars:before{content:"\f274"}.ion-contrast:before{content:"\f275"}.ion-crop:before{content:"\f3c1"}.ion-cube:before{content:"\f318"}.ion-disc:before{content:"\f12d"}.ion-document:before{content:"\f12f"}.ion-document-text:before{content:"\f12e"}.ion-drag:before{content:"\f130"}.ion-earth:before{content:"\f276"}.ion-easel:before{content:"\f3c2"}.ion-edit:before{content:"\f2bf"}.ion-egg:before{content:"\f277"}.ion-eject:before{content:"\f131"}.ion-email:before{content:"\f132"}.ion-email-unread:before{content:"\f3c3"}.ion-erlenmeyer-flask:before{content:"\f3c5"}.ion-erlenmeyer-flask-bubbles:before{content:"\f3c4"}.ion-eye:before{content:"\f133"}.ion-eye-disabled:before{content:"\f306"}.ion-female:before{content:"\f278"}.ion-filing:before{content:"\f134"}.ion-film-marker:before{content:"\f135"}.ion-fireball:before{content:"\f319"}.ion-flag:before{content:"\f279"}.ion-flame:before{content:"\f31a"}.ion-flash:before{content:"\f137"}.ion-flash-off:before{content:"\f136"}.ion-folder:before{content:"\f139"}.ion-fork:before{content:"\f27a"}.ion-fork-repo:before{content:"\f2c0"}.ion-forward:before{content:"\f13a"}.ion-funnel:before{content:"\f31b"}.ion-gear-a:before{content:"\f13d"}.ion-gear-b:before{content:"\f13e"}.ion-grid:before{content:"\f13f"}.ion-hammer:before{content:"\f27b"}.ion-happy:before{content:"\f31c"}.ion-happy-outline:before{content:"\f3c6"}.ion-headphone:before{content:"\f140"}.ion-heart:before{content:"\f141"}.ion-heart-broken:before{content:"\f31d"}.ion-help:before{content:"\f143"}.ion-help-buoy:before{content:"\f27c"}.ion-help-circled:before{content:"\f142"}.ion-home:before{content:"\f144"}.ion-icecream:before{content:"\f27d"}.ion-image:before{content:"\f147"}.ion-images:before{content:"\f148"}.ion-information:before{content:"\f14a"}.ion-information-circled:before{content:"\f149"}.ion-ionic:before{content:"\f14b"}.ion-ios-alarm:before{content:"\f3c8"}.ion-ios-alarm-outline:before{content:"\f3c7"}.ion-ios-albums:before{content:"\f3ca"}.ion-ios-albums-outline:before{content:"\f3c9"}.ion-ios-americanfootball:before{content:"\f3cc"}.ion-ios-americanfootball-outline:before{content:"\f3cb"}.ion-ios-analytics:before{content:"\f3ce"}.ion-ios-analytics-outline:before{content:"\f3cd"}.ion-ios-arrow-back:before{content:"\f3cf"}.ion-ios-arrow-down:before{content:"\f3d0"}.ion-ios-arrow-forward:before{content:"\f3d1"}.ion-ios-arrow-left:before{content:"\f3d2"}.ion-ios-arrow-right:before{content:"\f3d3"}.ion-ios-arrow-thin-down:before{content:"\f3d4"}.ion-ios-arrow-thin-left:before{content:"\f3d5"}.ion-ios-arrow-thin-right:before{content:"\f3d6"}.ion-ios-arrow-thin-up:before{content:"\f3d7"}.ion-ios-arrow-up:before{content:"\f3d8"}.ion-ios-at:before{content:"\f3da"}.ion-ios-at-outline:before{content:"\f3d9"}.ion-ios-barcode:before{content:"\f3dc"}.ion-ios-barcode-outline:before{content:"\f3db"}.ion-ios-baseball:before{content:"\f3de"}.ion-ios-baseball-outline:before{content:"\f3dd"}.ion-ios-basketball:before{content:"\f3e0"}.ion-ios-basketball-outline:before{content:"\f3df"}.ion-ios-bell:before{content:"\f3e2"}.ion-ios-bell-outline:before{content:"\f3e1"}.ion-ios-body:before{content:"\f3e4"}.ion-ios-body-outline:before{content:"\f3e3"}.ion-ios-bolt:before{content:"\f3e6"}.ion-ios-bolt-outline:before{content:"\f3e5"}.ion-ios-book:before{content:"\f3e8"}.ion-ios-book-outline:before{content:"\f3e7"}.ion-ios-bookmarks:before{content:"\f3ea"}.ion-ios-bookmarks-outline:before{content:"\f3e9"}.ion-ios-box:before{content:"\f3ec"}.ion-ios-box-outline:before{content:"\f3eb"}.ion-ios-briefcase:before{content:"\f3ee"}.ion-ios-briefcase-outline:before{content:"\f3ed"}.ion-ios-browsers:before{content:"\f3f0"}.ion-ios-browsers-outline:before{content:"\f3ef"}.ion-ios-calculator:before{content:"\f3f2"}.ion-ios-calculator-outline:before{content:"\f3f1"}.ion-ios-calendar:before{content:"\f3f4"}.ion-ios-calendar-outline:before{content:"\f3f3"}.ion-ios-camera:before{content:"\f3f6"}.ion-ios-camera-outline:before{content:"\f3f5"}.ion-ios-cart:before{content:"\f3f8"}.ion-ios-cart-outline:before{content:"\f3f7"}.ion-ios-chatboxes:before{content:"\f3fa"}.ion-ios-chatboxes-outline:before{content:"\f3f9"}.ion-ios-chatbubble:before{content:"\f3fc"}.ion-ios-chatbubble-outline:before{content:"\f3fb"}.ion-ios-checkmark:before{content:"\f3ff"}.ion-ios-checkmark-empty:before{content:"\f3fd"}.ion-ios-checkmark-outline:before{content:"\f3fe"}.ion-ios-circle-filled:before{content:"\f400"}.ion-ios-circle-outline:before{content:"\f401"}.ion-ios-clock:before{content:"\f403"}.ion-ios-clock-outline:before{content:"\f402"}.ion-ios-close:before{content:"\f406"}.ion-ios-close-empty:before{content:"\f404"}.ion-ios-close-outline:before{content:"\f405"}.ion-ios-cloud:before{content:"\f40c"}.ion-ios-cloud-download:before{content:"\f408"}.ion-ios-cloud-download-outline:before{content:"\f407"}.ion-ios-cloud-outline:before{content:"\f409"}.ion-ios-cloud-upload:before{content:"\f40b"}.ion-ios-cloud-upload-outline:before{content:"\f40a"}.ion-ios-cloudy:before{content:"\f410"}.ion-ios-cloudy-night:before{content:"\f40e"}.ion-ios-cloudy-night-outline:before{content:"\f40d"}.ion-ios-cloudy-outline:before{content:"\f40f"}.ion-ios-cog:before{content:"\f412"}.ion-ios-cog-outline:before{content:"\f411"}.ion-ios-color-filter:before{content:"\f414"}.ion-ios-color-filter-outline:before{content:"\f413"}.ion-ios-color-wand:before{content:"\f416"}.ion-ios-color-wand-outline:before{content:"\f415"}.ion-ios-compose:before{content:"\f418"}.ion-ios-compose-outline:before{content:"\f417"}.ion-ios-contact:before{content:"\f41a"}.ion-ios-contact-outline:before{content:"\f419"}.ion-ios-copy:before{content:"\f41c"}.ion-ios-copy-outline:before{content:"\f41b"}.ion-ios-crop:before{content:"\f41e"}.ion-ios-crop-strong:before{content:"\f41d"}.ion-ios-download:before{content:"\f420"}.ion-ios-download-outline:before{content:"\f41f"}.ion-ios-drag:before{content:"\f421"}.ion-ios-email:before{content:"\f423"}.ion-ios-email-outline:before{content:"\f422"}.ion-ios-eye:before{content:"\f425"}.ion-ios-eye-outline:before{content:"\f424"}.ion-ios-fastforward:before{content:"\f427"}.ion-ios-fastforward-outline:before{content:"\f426"}.ion-ios-filing:before{content:"\f429"}.ion-ios-filing-outline:before{content:"\f428"}.ion-ios-film:before{content:"\f42b"}.ion-ios-film-outline:before{content:"\f42a"}.ion-ios-flag:before{content:"\f42d"}.ion-ios-flag-outline:before{content:"\f42c"}.ion-ios-flame:before{content:"\f42f"}.ion-ios-flame-outline:before{content:"\f42e"}.ion-ios-flask:before{content:"\f431"}.ion-ios-flask-outline:before{content:"\f430"}.ion-ios-flower:before{content:"\f433"}.ion-ios-flower-outline:before{content:"\f432"}.ion-ios-folder:before{content:"\f435"}.ion-ios-folder-outline:before{content:"\f434"}.ion-ios-football:before{content:"\f437"}.ion-ios-football-outline:before{content:"\f436"}.ion-ios-game-controller-a:before{content:"\f439"}.ion-ios-game-controller-a-outline:before{content:"\f438"}.ion-ios-game-controller-b:before{content:"\f43b"}.ion-ios-game-controller-b-outline:before{content:"\f43a"}.ion-ios-gear:before{content:"\f43d"}.ion-ios-gear-outline:before{content:"\f43c"}.ion-ios-glasses:before{content:"\f43f"}.ion-ios-glasses-outline:before{content:"\f43e"}.ion-ios-grid-view:before{content:"\f441"}.ion-ios-grid-view-outline:before{content:"\f440"}.ion-ios-heart:before{content:"\f443"}.ion-ios-heart-outline:before{content:"\f442"}.ion-ios-help:before{content:"\f446"}.ion-ios-help-empty:before{content:"\f444"}.ion-ios-help-outline:before{content:"\f445"}.ion-ios-home:before{content:"\f448"}.ion-ios-home-outline:before{content:"\f447"}.ion-ios-infinite:before{content:"\f44a"}.ion-ios-infinite-outline:before{content:"\f449"}.ion-ios-information:before{content:"\f44d"}.ion-ios-information-empty:before{content:"\f44b"}.ion-ios-information-outline:before{content:"\f44c"}.ion-ios-ionic-outline:before{content:"\f44e"}.ion-ios-keypad:before{content:"\f450"}.ion-ios-keypad-outline:before{content:"\f44f"}.ion-ios-lightbulb:before{content:"\f452"}.ion-ios-lightbulb-outline:before{content:"\f451"}.ion-ios-list:before{content:"\f454"}.ion-ios-list-outline:before{content:"\f453"}.ion-ios-location:before{content:"\f456"}.ion-ios-location-outline:before{content:"\f455"}.ion-ios-locked:before{content:"\f458"}.ion-ios-locked-outline:before{content:"\f457"}.ion-ios-loop:before{content:"\f45a"}.ion-ios-loop-strong:before{content:"\f459"}.ion-ios-medical:before{content:"\f45c"}.ion-ios-medical-outline:before{content:"\f45b"}.ion-ios-medkit:before{content:"\f45e"}.ion-ios-medkit-outline:before{content:"\f45d"}.ion-ios-mic:before{content:"\f461"}.ion-ios-mic-off:before{content:"\f45f"}.ion-ios-mic-outline:before{content:"\f460"}.ion-ios-minus:before{content:"\f464"}.ion-ios-minus-empty:before{content:"\f462"}.ion-ios-minus-outline:before{content:"\f463"}.ion-ios-monitor:before{content:"\f466"}.ion-ios-monitor-outline:before{content:"\f465"}.ion-ios-moon:before{content:"\f468"}.ion-ios-moon-outline:before{content:"\f467"}.ion-ios-more:before{content:"\f46a"}.ion-ios-more-outline:before{content:"\f469"}.ion-ios-musical-note:before{content:"\f46b"}.ion-ios-musical-notes:before{content:"\f46c"}.ion-ios-navigate:before{content:"\f46e"}.ion-ios-navigate-outline:before{content:"\f46d"}.ion-ios-nutrition:before{content:"\f470"}.ion-ios-nutrition-outline:before{content:"\f46f"}.ion-ios-paper:before{content:"\f472"}.ion-ios-paper-outline:before{content:"\f471"}.ion-ios-paperplane:before{content:"\f474"}.ion-ios-paperplane-outline:before{content:"\f473"}.ion-ios-partlysunny:before{content:"\f476"}.ion-ios-partlysunny-outline:before{content:"\f475"}.ion-ios-pause:before{content:"\f478"}.ion-ios-pause-outline:before{content:"\f477"}.ion-ios-paw:before{content:"\f47a"}.ion-ios-paw-outline:before{content:"\f479"}.ion-ios-people:before{content:"\f47c"}.ion-ios-people-outline:before{content:"\f47b"}.ion-ios-person:before{content:"\f47e"}.ion-ios-person-outline:before{content:"\f47d"}.ion-ios-personadd:before{content:"\f480"}.ion-ios-personadd-outline:before{content:"\f47f"}.ion-ios-photos:before{content:"\f482"}.ion-ios-photos-outline:before{content:"\f481"}.ion-ios-pie:before{content:"\f484"}.ion-ios-pie-outline:before{content:"\f483"}.ion-ios-pint:before{content:"\f486"}.ion-ios-pint-outline:before{content:"\f485"}.ion-ios-play:before{content:"\f488"}.ion-ios-play-outline:before{content:"\f487"}.ion-ios-plus:before{content:"\f48b"}.ion-ios-plus-empty:before{content:"\f489"}.ion-ios-plus-outline:before{content:"\f48a"}.ion-ios-pricetag:before{content:"\f48d"}.ion-ios-pricetag-outline:before{content:"\f48c"}.ion-ios-pricetags:before{content:"\f48f"}.ion-ios-pricetags-outline:before{content:"\f48e"}.ion-ios-printer:before{content:"\f491"}.ion-ios-printer-outline:before{content:"\f490"}.ion-ios-pulse:before{content:"\f493"}.ion-ios-pulse-strong:before{content:"\f492"}.ion-ios-rainy:before{content:"\f495"}.ion-ios-rainy-outline:before{content:"\f494"}.ion-ios-recording:before{content:"\f497"}.ion-ios-recording-outline:before{content:"\f496"}.ion-ios-redo:before{content:"\f499"}.ion-ios-redo-outline:before{content:"\f498"}.ion-ios-refresh:before{content:"\f49c"}.ion-ios-refresh-empty:before{content:"\f49a"}.ion-ios-refresh-outline:before{content:"\f49b"}.ion-ios-reload:before{content:"\f49d"}.ion-ios-reverse-camera:before{content:"\f49f"}.ion-ios-reverse-camera-outline:before{content:"\f49e"}.ion-ios-rewind:before{content:"\f4a1"}.ion-ios-rewind-outline:before{content:"\f4a0"}.ion-ios-rose:before{content:"\f4a3"}.ion-ios-rose-outline:before{content:"\f4a2"}.ion-ios-search:before{content:"\f4a5"}.ion-ios-search-strong:before{content:"\f4a4"}.ion-ios-settings:before{content:"\f4a7"}.ion-ios-settings-strong:before{content:"\f4a6"}.ion-ios-shuffle:before{content:"\f4a9"}.ion-ios-shuffle-strong:before{content:"\f4a8"}.ion-ios-skipbackward:before{content:"\f4ab"}.ion-ios-skipbackward-outline:before{content:"\f4aa"}.ion-ios-skipforward:before{content:"\f4ad"}.ion-ios-skipforward-outline:before{content:"\f4ac"}.ion-ios-snowy:before{content:"\f4ae"}.ion-ios-speedometer:before{content:"\f4b0"}.ion-ios-speedometer-outline:before{content:"\f4af"}.ion-ios-star:before{content:"\f4b3"}.ion-ios-star-half:before{content:"\f4b1"}.ion-ios-star-outline:before{content:"\f4b2"}.ion-ios-stopwatch:before{content:"\f4b5"}.ion-ios-stopwatch-outline:before{content:"\f4b4"}.ion-ios-sunny:before{content:"\f4b7"}.ion-ios-sunny-outline:before{content:"\f4b6"}.ion-ios-telephone:before{content:"\f4b9"}.ion-ios-telephone-outline:before{content:"\f4b8"}.ion-ios-tennisball:before{content:"\f4bb"}.ion-ios-tennisball-outline:before{content:"\f4ba"}.ion-ios-thunderstorm:before{content:"\f4bd"}.ion-ios-thunderstorm-outline:before{content:"\f4bc"}.ion-ios-time:before{content:"\f4bf"}.ion-ios-time-outline:before{content:"\f4be"}.ion-ios-timer:before{content:"\f4c1"}.ion-ios-timer-outline:before{content:"\f4c0"}.ion-ios-toggle:before{content:"\f4c3"}.ion-ios-toggle-outline:before{content:"\f4c2"}.ion-ios-trash:before{content:"\f4c5"}.ion-ios-trash-outline:before{content:"\f4c4"}.ion-ios-undo:before{content:"\f4c7"}.ion-ios-undo-outline:before{content:"\f4c6"}.ion-ios-unlocked:before{content:"\f4c9"}.ion-ios-unlocked-outline:before{content:"\f4c8"}.ion-ios-upload:before{content:"\f4cb"}.ion-ios-upload-outline:before{content:"\f4ca"}.ion-ios-videocam:before{content:"\f4cd"}.ion-ios-videocam-outline:before{content:"\f4cc"}.ion-ios-volume-high:before{content:"\f4ce"}.ion-ios-volume-low:before{content:"\f4cf"}.ion-ios-wineglass:before{content:"\f4d1"}.ion-ios-wineglass-outline:before{content:"\f4d0"}.ion-ios-world:before{content:"\f4d3"}.ion-ios-world-outline:before{content:"\f4d2"}.ion-ipad:before{content:"\f1f9"}.ion-iphone:before{content:"\f1fa"}.ion-ipod:before{content:"\f1fb"}.ion-jet:before{content:"\f295"}.ion-key:before{content:"\f296"}.ion-knife:before{content:"\f297"}.ion-laptop:before{content:"\f1fc"}.ion-leaf:before{content:"\f1fd"}.ion-levels:before{content:"\f298"}.ion-lightbulb:before{content:"\f299"}.ion-link:before{content:"\f1fe"}.ion-load-a:before{content:"\f29a"}.ion-load-b:before{content:"\f29b"}.ion-load-c:before{content:"\f29c"}.ion-load-d:before{content:"\f29d"}.ion-location:before{content:"\f1ff"}.ion-lock-combination:before{content:"\f4d4"}.ion-locked:before{content:"\f200"}.ion-log-in:before{content:"\f29e"}.ion-log-out:before{content:"\f29f"}.ion-loop:before{content:"\f201"}.ion-magnet:before{content:"\f2a0"}.ion-male:before{content:"\f2a1"}.ion-man:before{content:"\f202"}.ion-map:before{content:"\f203"}.ion-medkit:before{content:"\f2a2"}.ion-merge:before{content:"\f33f"}.ion-mic-a:before{content:"\f204"}.ion-mic-b:before{content:"\f205"}.ion-mic-c:before{content:"\f206"}.ion-minus:before{content:"\f209"}.ion-minus-circled:before{content:"\f207"}.ion-minus-round:before{content:"\f208"}.ion-model-s:before{content:"\f2c1"}.ion-monitor:before{content:"\f20a"}.ion-more:before{content:"\f20b"}.ion-mouse:before{content:"\f340"}.ion-music-note:before{content:"\f20c"}.ion-navicon:before{content:"\f20e"}.ion-navicon-round:before{content:"\f20d"}.ion-navigate:before{content:"\f2a3"}.ion-network:before{content:"\f341"}.ion-no-smoking:before{content:"\f2c2"}.ion-nuclear:before{content:"\f2a4"}.ion-outlet:before{content:"\f342"}.ion-paintbrush:before{content:"\f4d5"}.ion-paintbucket:before{content:"\f4d6"}.ion-paper-airplane:before{content:"\f2c3"}.ion-paperclip:before{content:"\f20f"}.ion-pause:before{content:"\f210"}.ion-person:before{content:"\f213"}.ion-person-add:before{content:"\f211"}.ion-person-stalker:before{content:"\f212"}.ion-pie-graph:before{content:"\f2a5"}.ion-pin:before{content:"\f2a6"}.ion-pinpoint:before{content:"\f2a7"}.ion-pizza:before{content:"\f2a8"}.ion-plane:before{content:"\f214"}.ion-planet:before{content:"\f343"}.ion-play:before{content:"\f215"}.ion-playstation:before{content:"\f30a"}.ion-plus:before{content:"\f218"}.ion-plus-circled:before{content:"\f216"}.ion-plus-round:before{content:"\f217"}.ion-podium:before{content:"\f344"}.ion-pound:before{content:"\f219"}.ion-power:before{content:"\f2a9"}.ion-pricetag:before{content:"\f2aa"}.ion-pricetags:before{content:"\f2ab"}.ion-printer:before{content:"\f21a"}.ion-pull-request:before{content:"\f345"}.ion-qr-scanner:before{content:"\f346"}.ion-quote:before{content:"\f347"}.ion-radio-waves:before{content:"\f2ac"}.ion-record:before{content:"\f21b"}.ion-refresh:before{content:"\f21c"}.ion-reply:before{content:"\f21e"}.ion-reply-all:before{content:"\f21d"}.ion-ribbon-a:before{content:"\f348"}.ion-ribbon-b:before{content:"\f349"}.ion-sad:before{content:"\f34a"}.ion-sad-outline:before{content:"\f4d7"}.ion-scissors:before{content:"\f34b"}.ion-search:before{content:"\f21f"}.ion-settings:before{content:"\f2ad"}.ion-share:before{content:"\f220"}.ion-shuffle:before{content:"\f221"}.ion-skip-backward:before{content:"\f222"}.ion-skip-forward:before{content:"\f223"}.ion-social-android:before{content:"\f225"}.ion-social-android-outline:before{content:"\f224"}.ion-social-angular:before{content:"\f4d9"}.ion-social-angular-outline:before{content:"\f4d8"}.ion-social-apple:before{content:"\f227"}.ion-social-apple-outline:before{content:"\f226"}.ion-social-bitcoin:before{content:"\f2af"}.ion-social-bitcoin-outline:before{content:"\f2ae"}.ion-social-buffer:before{content:"\f229"}.ion-social-buffer-outline:before{content:"\f228"}.ion-social-chrome:before{content:"\f4db"}.ion-social-chrome-outline:before{content:"\f4da"}.ion-social-codepen:before{content:"\f4dd"}.ion-social-codepen-outline:before{content:"\f4dc"}.ion-social-css3:before{content:"\f4df"}.ion-social-css3-outline:before{content:"\f4de"}.ion-social-designernews:before{content:"\f22b"}.ion-social-designernews-outline:before{content:"\f22a"}.ion-social-dribbble:before{content:"\f22d"}.ion-social-dribbble-outline:before{content:"\f22c"}.ion-social-dropbox:before{content:"\f22f"}.ion-social-dropbox-outline:before{content:"\f22e"}.ion-social-euro:before{content:"\f4e1"}.ion-social-euro-outline:before{content:"\f4e0"}.ion-social-facebook:before{content:"\f231"}.ion-social-facebook-outline:before{content:"\f230"}.ion-social-foursquare:before{content:"\f34d"}.ion-social-foursquare-outline:before{content:"\f34c"}.ion-social-freebsd-devil:before{content:"\f2c4"}.ion-social-github:before{content:"\f233"}.ion-social-github-outline:before{content:"\f232"}.ion-social-google:before{content:"\f34f"}.ion-social-google-outline:before{content:"\f34e"}.ion-social-googleplus:before{content:"\f235"}.ion-social-googleplus-outline:before{content:"\f234"}.ion-social-hackernews:before{content:"\f237"}.ion-social-hackernews-outline:before{content:"\f236"}.ion-social-html5:before{content:"\f4e3"}.ion-social-html5-outline:before{content:"\f4e2"}.ion-social-instagram:before{content:"\f351"}.ion-social-instagram-outline:before{content:"\f350"}.ion-social-javascript:before{content:"\f4e5"}.ion-social-javascript-outline:before{content:"\f4e4"}.ion-social-linkedin:before{content:"\f239"}.ion-social-linkedin-outline:before{content:"\f238"}.ion-social-markdown:before{content:"\f4e6"}.ion-social-nodejs:before{content:"\f4e7"}.ion-social-octocat:before{content:"\f4e8"}.ion-social-pinterest:before{content:"\f2b1"}.ion-social-pinterest-outline:before{content:"\f2b0"}.ion-social-python:before{content:"\f4e9"}.ion-social-reddit:before{content:"\f23b"}.ion-social-reddit-outline:before{content:"\f23a"}.ion-social-rss:before{content:"\f23d"}.ion-social-rss-outline:before{content:"\f23c"}.ion-social-sass:before{content:"\f4ea"}.ion-social-skype:before{content:"\f23f"}.ion-social-skype-outline:before{content:"\f23e"}.ion-social-snapchat:before{content:"\f4ec"}.ion-social-snapchat-outline:before{content:"\f4eb"}.ion-social-tumblr:before{content:"\f241"}.ion-social-tumblr-outline:before{content:"\f240"}.ion-social-tux:before{content:"\f2c5"}.ion-social-twitch:before{content:"\f4ee"}.ion-social-twitch-outline:before{content:"\f4ed"}.ion-social-twitter:before{content:"\f243"}.ion-social-twitter-outline:before{content:"\f242"}.ion-social-usd:before{content:"\f353"}.ion-social-usd-outline:before{content:"\f352"}.ion-social-vimeo:before{content:"\f245"}.ion-social-vimeo-outline:before{content:"\f244"}.ion-social-whatsapp:before{content:"\f4f0"}.ion-social-whatsapp-outline:before{content:"\f4ef"}.ion-social-windows:before{content:"\f247"}.ion-social-windows-outline:before{content:"\f246"}.ion-social-wordpress:before{content:"\f249"}.ion-social-wordpress-outline:before{content:"\f248"}.ion-social-yahoo:before{content:"\f24b"}.ion-social-yahoo-outline:before{content:"\f24a"}.ion-social-yen:before{content:"\f4f2"}.ion-social-yen-outline:before{content:"\f4f1"}.ion-social-youtube:before{content:"\f24d"}.ion-social-youtube-outline:before{content:"\f24c"}.ion-soup-can:before{content:"\f4f4"}.ion-soup-can-outline:before{content:"\f4f3"}.ion-speakerphone:before{content:"\f2b2"}.ion-speedometer:before{content:"\f2b3"}.ion-spoon:before{content:"\f2b4"}.ion-star:before{content:"\f24e"}.ion-stats-bars:before{content:"\f2b5"}.ion-steam:before{content:"\f30b"}.ion-stop:before{content:"\f24f"}.ion-thermometer:before{content:"\f2b6"}.ion-thumbsdown:before{content:"\f250"}.ion-thumbsup:before{content:"\f251"}.ion-toggle:before{content:"\f355"}.ion-toggle-filled:before{content:"\f354"}.ion-transgender:before{content:"\f4f5"}.ion-trash-a:before{content:"\f252"}.ion-trash-b:before{content:"\f253"}.ion-trophy:before{content:"\f356"}.ion-tshirt:before{content:"\f4f7"}.ion-tshirt-outline:before{content:"\f4f6"}.ion-umbrella:before{content:"\f2b7"}.ion-university:before{content:"\f357"}.ion-unlocked:before{content:"\f254"}.ion-upload:before{content:"\f255"}.ion-usb:before{content:"\f2b8"}.ion-videocamera:before{content:"\f256"}.ion-volume-high:before{content:"\f257"}.ion-volume-low:before{content:"\f258"}.ion-volume-medium:before{content:"\f259"}.ion-volume-mute:before{content:"\f25a"}.ion-wand:before{content:"\f358"}.ion-waterdrop:before{content:"\f25b"}.ion-wifi:before{content:"\f25c"}.ion-wineglass:before{content:"\f2b9"}.ion-woman:before{content:"\f25d"}.ion-wrench:before{content:"\f2ba"}.ion-xbox:before{content:"\f30c"} 12 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /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/linguowei/micro-note/bbca53bc7f164966434107823ee568f443e370e4/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | micro-node 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule); 12 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** Evergreen browsers require these. **/ 41 | import 'core-js/es6/reflect'; 42 | import 'core-js/es7/reflect'; 43 | 44 | 45 | /** 46 | * Required to support Web Animations `@angular/animation`. 47 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 48 | **/ 49 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 50 | 51 | 52 | 53 | /*************************************************************************************************** 54 | * Zone JS is required by Angular itself. 55 | */ 56 | import 'zone.js/dist/zone'; // Included with Angular CLI. 57 | 58 | 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | 64 | /** 65 | * Date, currency, decimal and percent pipes. 66 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10 67 | */ 68 | // import 'intl'; // Run `npm install --save intl`. 69 | /** 70 | * Need to import at least one locale-data with intl. 71 | */ 72 | // import 'intl/locale-data/jsonp/en'; 73 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | @import './assets/ionicons.min.css'; /* 图标库 */ 3 | *{ 4 | -webkit-font-smoothing: antialiased; 5 | -moz-osx-font-smoothing: grayscale; 6 | list-style: none; 7 | margin: 0; 8 | padding: 0; 9 | line-height: 1.5; 10 | &::-webkit-scrollbar{ 11 | width: 0; 12 | height: 0; 13 | } 14 | } 15 | 16 | body{ 17 | font-size: 12px; 18 | font-family: -apple-system,"Helvetica Neue",Helvetica,Arial,"PingFang SC","Hiragino Sans GB","WenQuanYi Micro Hei","Microsoft Yahei",sans-serif; 19 | } 20 | a{ 21 | text-decoration: none; 22 | } 23 | a:hover, a:visited, a:link, a:active { 24 | color: #495060 25 | } 26 | .my-loading-bar{ 27 | height: 2px; 28 | background-color: #3db8c1; 29 | position: fixed; 30 | top: 0; 31 | left: 0; 32 | } 33 | 34 | // msg-style 35 | .msg-wrap{ 36 | width: 100vw; 37 | position: fixed; 38 | top: 16px; 39 | left: 0; 40 | pointer-events: none; 41 | display: flex; 42 | justify-content: center; 43 | align-items: center; 44 | z-index: 103; 45 | .msg-content{ 46 | border-radius: 4px; 47 | box-shadow: 0px 0px 6px #ccc; 48 | background-color: #fff; 49 | padding: 6px; 50 | text-align: center; 51 | display: inline-block; 52 | min-width: 200px; 53 | font-size: 14px; 54 | color: #5a5a5a; 55 | i{ 56 | margin-right: 5px; 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 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 const __karma__: any; 17 | declare const require: any; 18 | 19 | // Prevent Karma from running prematurely. 20 | __karma__.loaded = function () {}; 21 | 22 | // First, initialize the Angular testing environment. 23 | getTestBed().initTestEnvironment( 24 | BrowserDynamicTestingModule, 25 | platformBrowserDynamicTesting() 26 | ); 27 | // Then we find all the tests. 28 | const context = require.context('./', true, /\.spec\.ts$/); 29 | // And load the modules. 30 | context.keys().map(context); 31 | // Finally, start Karma to run the tests. 32 | __karma__.start(); 33 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "baseUrl": "./", 6 | "module": "es2015", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "node" 11 | ] 12 | }, 13 | "files": [ 14 | "test.ts" 15 | ], 16 | "include": [ 17 | "**/*.spec.ts", 18 | "**/*.d.ts" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "target": "es5", 11 | "typeRoots": [ 12 | "node_modules/@types" 13 | ], 14 | "lib": [ 15 | "es2016", 16 | "dom" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "eofline": true, 15 | "forin": true, 16 | "import-blacklist": [ 17 | true, 18 | "rxjs" 19 | ], 20 | "import-spacing": true, 21 | "indent": [ 22 | true, 23 | "spaces" 24 | ], 25 | "interface-over-type-literal": true, 26 | "label-position": true, 27 | "max-line-length": [ 28 | true, 29 | 140 30 | ], 31 | "member-access": false, 32 | "member-ordering": [ 33 | true, 34 | { 35 | "order": [ 36 | "static-field", 37 | "instance-field", 38 | "static-method", 39 | "instance-method" 40 | ] 41 | } 42 | ], 43 | "no-arg": true, 44 | "no-bitwise": true, 45 | "no-console": [ 46 | true, 47 | "debug", 48 | "info", 49 | "time", 50 | "timeEnd", 51 | "trace" 52 | ], 53 | "no-construct": true, 54 | "no-debugger": true, 55 | "no-duplicate-super": true, 56 | "no-empty": false, 57 | "no-empty-interface": true, 58 | "no-eval": true, 59 | "no-inferrable-types": [ 60 | true, 61 | "ignore-params" 62 | ], 63 | "no-misused-new": true, 64 | "no-non-null-assertion": true, 65 | "no-shadowed-variable": true, 66 | "no-string-literal": false, 67 | "no-string-throw": true, 68 | "no-switch-case-fall-through": true, 69 | "no-trailing-whitespace": true, 70 | "no-unnecessary-initializer": true, 71 | "no-unused-expression": true, 72 | "no-use-before-declare": true, 73 | "no-var-keyword": true, 74 | "object-literal-sort-keys": false, 75 | "one-line": [ 76 | true, 77 | "check-open-brace", 78 | "check-catch", 79 | "check-else", 80 | "check-whitespace" 81 | ], 82 | "prefer-const": true, 83 | "quotemark": [ 84 | true, 85 | "single" 86 | ], 87 | "radix": true, 88 | "semicolon": [ 89 | true, 90 | "always" 91 | ], 92 | "triple-equals": [ 93 | true, 94 | "allow-null-check" 95 | ], 96 | "typedef-whitespace": [ 97 | true, 98 | { 99 | "call-signature": "nospace", 100 | "index-signature": "nospace", 101 | "parameter": "nospace", 102 | "property-declaration": "nospace", 103 | "variable-declaration": "nospace" 104 | } 105 | ], 106 | "typeof-compare": true, 107 | "unified-signatures": true, 108 | "variable-name": false, 109 | "whitespace": [ 110 | true, 111 | "check-branch", 112 | "check-decl", 113 | "check-operator", 114 | "check-separator", 115 | "check-type" 116 | ], 117 | "directive-selector": [ 118 | true, 119 | "attribute", 120 | "app", 121 | "camelCase" 122 | ], 123 | "component-selector": [ 124 | true, 125 | "element", 126 | "app", 127 | "kebab-case" 128 | ], 129 | "use-input-property-decorator": true, 130 | "use-output-property-decorator": true, 131 | "use-host-property-decorator": true, 132 | "no-input-rename": true, 133 | "no-output-rename": true, 134 | "use-life-cycle-interface": true, 135 | "use-pipe-transform-interface": true, 136 | "component-class-suffix": true, 137 | "directive-class-suffix": true, 138 | "no-access-missing-member": true, 139 | "templates-use-public": true, 140 | "invoke-injectable": true 141 | } 142 | } 143 | --------------------------------------------------------------------------------