├── .DS_Store ├── .gitignore ├── .idea ├── blog.iml ├── modules.xml └── workspace.xml ├── README.md ├── api ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .yo-rc.json ├── README.md ├── client │ └── README.md ├── common │ └── models │ │ ├── account.js │ │ ├── account.json │ │ ├── category.js │ │ ├── category.json │ │ ├── comment.js │ │ ├── comment.json │ │ ├── post.js │ │ └── post.json ├── package.json └── server │ ├── boot │ ├── authentication.js │ ├── install.js │ └── root.js │ ├── component-config.json │ ├── config.json │ ├── datasources.json │ ├── middleware.development.json │ ├── middleware.json │ ├── model-config.json │ └── server.js └── site ├── .editorconfig ├── .gitignore ├── README.md ├── angular-cli.json ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.json ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── src ├── app │ ├── app.component.css │ ├── app.component.html │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── blog │ │ ├── blog │ │ │ ├── blog.component.css │ │ │ ├── blog.component.html │ │ │ ├── blog.component.spec.ts │ │ │ ├── blog.component.ts │ │ │ └── post.ts │ │ ├── category-view │ │ │ ├── category-view.component.css │ │ │ ├── category-view.component.html │ │ │ ├── category-view.component.spec.ts │ │ │ └── category-view.component.ts │ │ ├── category.model.ts │ │ ├── comment │ │ │ ├── comment-form │ │ │ │ ├── comment-form.component.css │ │ │ │ ├── comment-form.component.html │ │ │ │ ├── comment-form.component.spec.ts │ │ │ │ └── comment-form.component.ts │ │ │ └── comment.ts │ │ ├── post-detail │ │ │ ├── post-detail.component.css │ │ │ ├── post-detail.component.html │ │ │ ├── post-detail.component.spec.ts │ │ │ └── post-detail.component.ts │ │ ├── post-form │ │ │ ├── post-form.component.css │ │ │ ├── post-form.component.html │ │ │ ├── post-form.component.spec.ts │ │ │ └── post-form.component.ts │ │ ├── post.service.spec.ts │ │ └── post.service.ts │ ├── helpers │ │ └── editor │ │ │ ├── editor.component.css │ │ │ ├── editor.component.html │ │ │ ├── editor.component.spec.ts │ │ │ └── editor.component.ts │ ├── home │ │ ├── home.component.css │ │ ├── home.component.html │ │ ├── home.component.spec.ts │ │ └── home.component.ts │ ├── index.ts │ └── user │ │ ├── auth.service.spec.ts │ │ ├── auth.service.ts │ │ ├── login │ │ ├── login.component.css │ │ ├── login.component.html │ │ ├── login.component.spec.ts │ │ └── login.component.ts │ │ ├── profile │ │ ├── profile.component.css │ │ ├── profile.component.html │ │ ├── profile.component.spec.ts │ │ └── profile.component.ts │ │ ├── register │ │ ├── register.component.css │ │ ├── register.component.html │ │ ├── register.component.spec.ts │ │ └── register.component.ts │ │ ├── user-posts │ │ ├── user-posts.component.css │ │ ├── user-posts.component.html │ │ ├── user-posts.component.spec.ts │ │ └── user-posts.component.ts │ │ ├── user.service.spec.ts │ │ ├── user.service.ts │ │ └── user.ts ├── assets │ ├── .gitkeep │ └── skins │ │ └── lightgray │ │ ├── content.inline.min.css │ │ ├── content.min.css │ │ ├── fonts │ │ ├── tinymce-small.eot │ │ ├── tinymce-small.svg │ │ ├── tinymce-small.ttf │ │ ├── tinymce-small.woff │ │ ├── tinymce.eot │ │ ├── tinymce.svg │ │ ├── tinymce.ttf │ │ └── tinymce.woff │ │ ├── img │ │ ├── anchor.gif │ │ ├── loader.gif │ │ ├── object.gif │ │ └── trans.gif │ │ ├── skin.ie7.min.css │ │ └── skin.min.css ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.css ├── test.ts ├── tsconfig.json └── typings.d.ts └── tslint.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | -------------------------------------------------------------------------------- /.idea/blog.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build your own blog with Angular 2 and StrongLoop(Loopback) & Mongodb. 2 | 3 | Tutorial build blog with Anglar 2, Loopback (Strongloop) 4 | 5 | Todos: 6 | * Install http://nodejs.org/ 7 | * Install http://loopback.io/ 8 | * Install Angular2 Cli http://cli.angular.io/ 9 | 10 | 11 | Build Blog with Angular 2 + Strongloop(Loopback) Mongodb 12 | ## Installation 13 | 14 | * Clone project 15 | ```python 16 | git clone git@github.com:tabvn/angular-blog.git blog 17 | cd blog/api 18 | npm install 19 | 20 | ``` 21 | * Config database connection in server/datasources.json must change the info. 22 | * run the api server 23 | ```python 24 | cd api 25 | npm start 26 | ``` 27 | * Run the angular 2 app by open new tab in terminal 28 | ```python 29 | cd ../site 30 | npm install 31 | npm start 32 | ``` 33 | 34 | ## Tutorials 35 | * Tutorial: https://www.youtube.com/watch?v=sFpwxTdy9gQ&list=PLFaW_8zE4amNEdKZOJD3P_GeV3Hgva7RD 36 | * File upload: https://github.com/tabvn/angular-blog/wiki/LoopBack-File-upload---Angular-2 37 | -------------------------------------------------------------------------------- /api/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | -------------------------------------------------------------------------------- /api/.eslintignore: -------------------------------------------------------------------------------- 1 | /client/ -------------------------------------------------------------------------------- /api/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "loopback" 3 | } -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | *.csv 2 | *.dat 3 | *.iml 4 | *.log 5 | *.out 6 | *.pid 7 | *.seed 8 | *.sublime-* 9 | *.swo 10 | *.swp 11 | *.tgz 12 | *.xml 13 | .DS_Store 14 | .idea 15 | .project 16 | .strong-pm 17 | coverage 18 | node_modules 19 | npm-debug.log 20 | -------------------------------------------------------------------------------- /api/.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-loopback": {} 3 | } -------------------------------------------------------------------------------- /api/README.md: -------------------------------------------------------------------------------- 1 | # My Application 2 | 3 | The project is generated by [LoopBack](http://loopback.io). -------------------------------------------------------------------------------- /api/client/README.md: -------------------------------------------------------------------------------- 1 | ## Client 2 | 3 | This is the place for your application front-end files. 4 | -------------------------------------------------------------------------------- /api/common/models/account.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(Account) { 4 | 5 | }; 6 | -------------------------------------------------------------------------------- /api/common/models/account.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "account", 3 | "base": "User", 4 | "idInjection": true, 5 | "options": { 6 | "validateUpsert": true 7 | }, 8 | "protected": [ 9 | "email" 10 | ], 11 | "properties": { 12 | "firstName": { 13 | "type": "string" 14 | }, 15 | "lastName": { 16 | "type": "string" 17 | } 18 | }, 19 | "validations": [], 20 | "relations": { 21 | "posts": { 22 | "type": "hasMany", 23 | "model": "post", 24 | "foreignKey": "" 25 | } 26 | }, 27 | "acls": [ 28 | { 29 | "accessType": "*", 30 | "principalType": "ROLE", 31 | "principalId": "administrator", 32 | "permission": "ALLOW" 33 | }, 34 | { 35 | "principalType": "ROLE", 36 | "principalId": "$everyone", 37 | "permission": "ALLOW", 38 | "property": "findById" 39 | }, 40 | { 41 | "principalType": "ROLE", 42 | "principalId": "$everyone", 43 | "permission": "ALLOW", 44 | "property": "__get__posts" 45 | }, 46 | { 47 | "principalType": "ROLE", 48 | "principalId": "$owner", 49 | "permission": "ALLOW", 50 | "property": [ 51 | "__create__posts", 52 | "__delete__posts", 53 | "__destroyById__posts", 54 | "__findById__posts", 55 | "__get__posts", 56 | "__updateById__posts" 57 | ] 58 | } 59 | ], 60 | "methods": {} 61 | } 62 | -------------------------------------------------------------------------------- /api/common/models/category.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(Category) { 4 | 5 | }; 6 | -------------------------------------------------------------------------------- /api/common/models/category.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "category", 3 | "base": "PersistedModel", 4 | "idInjection": true, 5 | "options": { 6 | "validateUpsert": true 7 | }, 8 | "properties": { 9 | "title": { 10 | "type": "string" 11 | }, 12 | "description": { 13 | "type": "string" 14 | } 15 | }, 16 | "validations": [], 17 | "relations": { 18 | "posts": { 19 | "type": "hasMany", 20 | "model": "post", 21 | "foreignKey": "" 22 | } 23 | }, 24 | "acls": [], 25 | "methods": {} 26 | } 27 | -------------------------------------------------------------------------------- /api/common/models/comment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(Comment) { 4 | 5 | }; 6 | -------------------------------------------------------------------------------- /api/common/models/comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "comment", 3 | "base": "PersistedModel", 4 | "idInjection": true, 5 | "options": { 6 | "validateUpsert": true 7 | }, 8 | "properties": { 9 | "title": { 10 | "type": "string" 11 | }, 12 | "body": { 13 | "type": "string" 14 | }, 15 | "name": { 16 | "type": "string" 17 | } 18 | }, 19 | "validations": [], 20 | "relations": { 21 | "post": { 22 | "type": "belongsTo", 23 | "model": "post", 24 | "foreignKey": "" 25 | } 26 | }, 27 | "acls": [ 28 | { 29 | "accessType": "*", 30 | "principalType": "ROLE", 31 | "principalId": "$everyone", 32 | "permission": "ALLOW" 33 | } 34 | ], 35 | "methods": {} 36 | } 37 | -------------------------------------------------------------------------------- /api/common/models/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(Post) { 4 | 5 | }; 6 | -------------------------------------------------------------------------------- /api/common/models/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "post", 3 | "base": "PersistedModel", 4 | "idInjection": true, 5 | "options": { 6 | "validateUpsert": true 7 | }, 8 | "properties": { 9 | "title": { 10 | "type": "string", 11 | "required": true 12 | }, 13 | "body": { 14 | "type": "string" 15 | } 16 | }, 17 | "validations": [], 18 | "relations": { 19 | "account": { 20 | "type": "belongsTo", 21 | "model": "account", 22 | "foreignKey": "" 23 | }, 24 | "category": { 25 | "type": "belongsTo", 26 | "model": "category", 27 | "foreignKey": "" 28 | }, 29 | "comments": { 30 | "type": "hasMany", 31 | "model": "comment", 32 | "foreignKey": "" 33 | } 34 | }, 35 | "acls": [ 36 | { 37 | "accessType": "*", 38 | "principalType": "ROLE", 39 | "principalId": "administrator", 40 | "permission": "ALLOW" 41 | }, 42 | { 43 | "accessType": "*", 44 | "principalType": "ROLE", 45 | "principalId": "$everyone", 46 | "permission": "DENY" 47 | }, 48 | { 49 | "accessType": "READ", 50 | "principalType": "ROLE", 51 | "principalId": "$everyone", 52 | "permission": "ALLOW" 53 | } 54 | , 55 | { 56 | "principalType": "ROLE", 57 | "principalId": "$everyone", 58 | "permission": "ALLOW", 59 | "property": "__create__comments" 60 | }, 61 | { 62 | "principalType": "ROLE", 63 | "principalId": "$everyone", 64 | "permission": "ALLOW", 65 | "property": "__delete__comments" 66 | } 67 | 68 | 69 | ], 70 | "methods": {} 71 | } 72 | -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "1.0.0", 4 | "main": "server/server.js", 5 | "scripts": { 6 | "lint": "eslint .", 7 | "start": "node .", 8 | "posttest": "npm run lint && nsp check" 9 | }, 10 | "dependencies": { 11 | "compression": "^1.0.3", 12 | "cors": "^2.5.2", 13 | "helmet": "^1.3.0", 14 | "loopback-boot": "^2.6.5", 15 | "loopback-component-explorer": "^2.4.0", 16 | "serve-favicon": "^2.0.1", 17 | "strong-error-handler": "^1.0.1", 18 | "loopback-datasource-juggler": "^2.39.0", 19 | "loopback": "^2.22.0", 20 | "loopback-connector-mongodb": "*" 21 | }, 22 | "devDependencies": { 23 | "eslint": "^2.13.1", 24 | "eslint-config-loopback": "^4.0.0", 25 | "nsp": "^2.1.0" 26 | }, 27 | "repository": { 28 | "type": "", 29 | "url": "" 30 | }, 31 | "license": "UNLICENSED", 32 | "description": "api" 33 | } 34 | -------------------------------------------------------------------------------- /api/server/boot/authentication.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function enableAuthentication(server) { 4 | // enable authentication 5 | server.enableAuth(); 6 | }; 7 | -------------------------------------------------------------------------------- /api/server/boot/install.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var installed = true; // set to the true if we have ran installed before. 4 | module.exports = function (app) { 5 | 6 | 7 | 8 | if (!installed) { 9 | var User = app.models.account; 10 | var Role = app.models.Role; 11 | var RoleMapping = app.models.RoleMapping; 12 | 13 | 14 | User.create([ 15 | {username: 'admin', email: 'toan@tabvn.com', password: 'admin', 'firstName': 'Toan', 'lastName': 'Nguyen Dinh'}, 16 | ], function (err, users) { 17 | if (err) throw err; 18 | 19 | 20 | console.log("Created User: ", users); 21 | //create the admin role 22 | Role.create({ 23 | name: 'administrator' 24 | }, function (err, role) { 25 | if (err) throw err; 26 | 27 | //make bob an admin 28 | role.principals.create({ 29 | principalType: RoleMapping.USER, 30 | principalId: users[0].id 31 | }, function (err, principal) { 32 | console.log('Created principal:', principal); 33 | 34 | // now it should be fine :) 35 | }); 36 | }); 37 | }); 38 | } 39 | 40 | 41 | }; 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /api/server/boot/root.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(server) { 4 | // Install a `/` route that returns server status 5 | var router = server.loopback.Router(); 6 | router.get('/', server.loopback.status()); 7 | server.use(router); 8 | }; 9 | -------------------------------------------------------------------------------- /api/server/component-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "loopback-component-explorer": { 3 | "mountPath": "/explorer" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /api/server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "restApiRoot": "/api", 3 | "host": "0.0.0.0", 4 | "port": 3000, 5 | "remoting": { 6 | "context": false, 7 | "rest": { 8 | "normalizeHttpPath": false, 9 | "xml": false 10 | }, 11 | "json": { 12 | "strict": false, 13 | "limit": "100kb" 14 | }, 15 | "urlencoded": { 16 | "extended": true, 17 | "limit": "100kb" 18 | }, 19 | "cors": false, 20 | "handleErrors": false 21 | }, 22 | "legacyExplorer": false 23 | } 24 | -------------------------------------------------------------------------------- /api/server/datasources.json: -------------------------------------------------------------------------------- 1 | { 2 | "db": { 3 | "host": "ds161497.mlab.com", 4 | "port": 61497, 5 | "database": "blog", 6 | "password": "test", 7 | "name": "db", 8 | "connector": "mongodb", 9 | "user": "test" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /api/server/middleware.development.json: -------------------------------------------------------------------------------- 1 | { 2 | "final:after": { 3 | "strong-error-handler": { 4 | "params": { 5 | "debug": true, 6 | "log": true 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /api/server/middleware.json: -------------------------------------------------------------------------------- 1 | { 2 | "initial:before": { 3 | "loopback#favicon": {} 4 | }, 5 | "initial": { 6 | "compression": {}, 7 | "cors": { 8 | "params": { 9 | "origin": true, 10 | "credentials": true, 11 | "maxAge": 86400 12 | } 13 | }, 14 | "helmet#xssFilter": {}, 15 | "helmet#frameguard": { 16 | "params": [ 17 | "deny" 18 | ] 19 | }, 20 | "helmet#hsts": { 21 | "params": { 22 | "maxAge": 0, 23 | "includeSubdomains": true 24 | } 25 | }, 26 | "helmet#hidePoweredBy": {}, 27 | "helmet#ieNoOpen": {}, 28 | "helmet#noSniff": {}, 29 | "helmet#noCache": { 30 | "enabled": false 31 | } 32 | }, 33 | "session": {}, 34 | "auth": {}, 35 | "parse": {}, 36 | "routes": { 37 | "loopback#rest": { 38 | "paths": [ 39 | "${restApiRoot}" 40 | ] 41 | } 42 | }, 43 | "files": {}, 44 | "final": { 45 | "loopback#urlNotFound": {} 46 | }, 47 | "final:after": { 48 | "strong-error-handler": {} 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /api/server/model-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "sources": [ 4 | "loopback/common/models", 5 | "loopback/server/models", 6 | "../common/models", 7 | "./models" 8 | ], 9 | "mixins": [ 10 | "loopback/common/mixins", 11 | "loopback/server/mixins", 12 | "../common/mixins", 13 | "./mixins" 14 | ] 15 | }, 16 | "User": { 17 | "dataSource": "db", 18 | "public": false 19 | }, 20 | "AccessToken": { 21 | "dataSource": "db", 22 | "public": false 23 | }, 24 | "ACL": { 25 | "dataSource": "db", 26 | "public": false 27 | }, 28 | "RoleMapping": { 29 | "dataSource": "db", 30 | "public": false 31 | }, 32 | "Role": { 33 | "dataSource": "db", 34 | "public": false 35 | }, 36 | "post": { 37 | "dataSource": "db", 38 | "public": true 39 | }, 40 | "account": { 41 | "dataSource": "db", 42 | "public": true 43 | }, 44 | "category": { 45 | "dataSource": "db", 46 | "public": true 47 | }, 48 | "comment": { 49 | "dataSource": "db", 50 | "public": true 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /api/server/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var loopback = require('loopback'); 4 | var boot = require('loopback-boot'); 5 | 6 | var app = module.exports = loopback(); 7 | 8 | app.start = function() { 9 | // start the web server 10 | return app.listen(function() { 11 | app.emit('started'); 12 | var baseUrl = app.get('url').replace(/\/$/, ''); 13 | console.log('Web server listening at: %s', baseUrl); 14 | if (app.get('loopback-component-explorer')) { 15 | var explorerPath = app.get('loopback-component-explorer').mountPath; 16 | console.log('Browse your REST API at %s%s', baseUrl, explorerPath); 17 | } 18 | }); 19 | }; 20 | 21 | // Bootstrap the application, configure models, datasources and middleware. 22 | // Sub-apps like REST API are mounted via boot scripts. 23 | boot(app, __dirname, function(err) { 24 | if (err) throw err; 25 | 26 | // start the server if `$ node server.js` 27 | if (require.main === module) 28 | app.start(); 29 | }); 30 | -------------------------------------------------------------------------------- /site/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = 0 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # IDEs and editors 12 | /.idea 13 | /.vscode 14 | .project 15 | .classpath 16 | *.launch 17 | .settings/ 18 | 19 | # misc 20 | /.sass-cache 21 | /connect.lock 22 | /coverage/* 23 | /libpeerconnection.log 24 | npm-debug.log 25 | testem.log 26 | /typings 27 | 28 | # e2e 29 | /e2e/*.js 30 | /e2e/*.map 31 | 32 | #System Files 33 | .DS_Store 34 | Thumbs.db 35 | -------------------------------------------------------------------------------- /site/README.md: -------------------------------------------------------------------------------- 1 | # Blog 2 | 3 | This project was generated with [angular-cli](https://github.com/angular/angular-cli) version 1.0.0-beta.19-3. 4 | 5 | ## Development server 6 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 7 | 8 | ## Code scaffolding 9 | 10 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class`. 11 | 12 | ## Build 13 | 14 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build. 15 | 16 | ## Running unit tests 17 | 18 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 19 | 20 | ## Running end-to-end tests 21 | 22 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 23 | Before running the tests make sure you are serving the app via `ng serve`. 24 | 25 | ## Deploying to Github Pages 26 | 27 | Run `ng github-pages:deploy` to deploy to Github Pages. 28 | 29 | ## Further help 30 | 31 | To get more help on the `angular-cli` use `ng --help` or go check out the [Angular-CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 32 | -------------------------------------------------------------------------------- /site/angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "version": "1.0.0-beta.19-3", 4 | "name": "blog" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "test": "test.ts", 17 | "tsconfig": "tsconfig.json", 18 | "prefix": "app", 19 | "mobile": false, 20 | "styles": [ 21 | "styles.css" 22 | ], 23 | "scripts": [ 24 | "../node_modules/tinymce/tinymce.js", 25 | "../node_modules/tinymce/themes/modern/theme.js", 26 | "../node_modules/tinymce/plugins/link/plugin.js", 27 | "../node_modules/tinymce/plugins/paste/plugin.js", 28 | "../node_modules/tinymce/plugins/table/plugin.js" 29 | ], 30 | "environments": { 31 | "source": "environments/environment.ts", 32 | "dev": "environments/environment.ts", 33 | "prod": "environments/environment.prod.ts" 34 | } 35 | } 36 | ], 37 | "addons": [], 38 | "packages": [], 39 | "e2e": { 40 | "protractor": { 41 | "config": "./protractor.conf.js" 42 | } 43 | }, 44 | "test": { 45 | "karma": { 46 | "config": "./karma.conf.js" 47 | } 48 | }, 49 | "defaults": { 50 | "styleExt": "css", 51 | "prefixInterfaces": false, 52 | "inline": { 53 | "style": false, 54 | "template": false 55 | }, 56 | "spec": { 57 | "class": false, 58 | "component": true, 59 | "directive": true, 60 | "module": false, 61 | "pipe": true, 62 | "service": true 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /site/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { BlogPage } from './app.po'; 2 | 3 | describe('blog App', function() { 4 | let page: BlogPage; 5 | 6 | beforeEach(() => { 7 | page = new BlogPage(); 8 | }); 9 | 10 | it('should display message saying app works', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('app works!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /site/e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class BlogPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /site/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "outDir": "../dist/out-tsc-e2e", 10 | "sourceMap": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "../node_modules/@types" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /site/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', 'angular-cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-remap-istanbul'), 12 | require('angular-cli/plugins/karma') 13 | ], 14 | files: [ 15 | { pattern: './src/test.ts', watched: false } 16 | ], 17 | preprocessors: { 18 | './src/test.ts': ['angular-cli'] 19 | }, 20 | remapIstanbulReporter: { 21 | reports: { 22 | html: 'coverage', 23 | lcovonly: './coverage/coverage.lcov' 24 | } 25 | }, 26 | angularCli: { 27 | config: './angular-cli.json', 28 | environment: 'dev' 29 | }, 30 | reporters: config.angularCli && config.angularCli.codeCoverage 31 | ? ['progress', 'karma-remap-istanbul'] 32 | : ['progress'], 33 | port: 9876, 34 | colors: true, 35 | logLevel: config.LOG_INFO, 36 | autoWatch: true, 37 | browsers: ['Chrome'], 38 | singleRun: false 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "angular-cli": {}, 6 | "scripts": { 7 | "start": "ng serve", 8 | "lint": "tslint \"src/**/*.ts\"", 9 | "test": "ng test", 10 | "pree2e": "webdriver-manager update", 11 | "e2e": "protractor" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/common": "~2.1.0", 16 | "@angular/compiler": "~2.1.0", 17 | "@angular/core": "~2.1.0", 18 | "@angular/forms": "~2.1.0", 19 | "@angular/http": "~2.1.0", 20 | "@angular/platform-browser": "~2.1.0", 21 | "@angular/platform-browser-dynamic": "~2.1.0", 22 | "@angular/router": "~3.1.0", 23 | "core-js": "^2.4.1", 24 | "rxjs": "5.0.0-beta.12", 25 | "tinymce": "^4.5.0", 26 | "ts-helpers": "^1.1.1", 27 | "zone.js": "^0.6.23" 28 | }, 29 | "devDependencies": { 30 | "@types/jasmine": "^2.2.30", 31 | "@types/node": "^6.0.42", 32 | "angular-cli": "1.0.0-beta.19-3", 33 | "codelyzer": "1.0.0-beta.1", 34 | "jasmine-core": "2.4.1", 35 | "jasmine-spec-reporter": "2.5.0", 36 | "karma": "1.2.0", 37 | "karma-chrome-launcher": "^2.0.0", 38 | "karma-cli": "^1.0.1", 39 | "karma-jasmine": "^1.0.2", 40 | "karma-remap-istanbul": "^0.2.1", 41 | "protractor": "4.0.9", 42 | "ts-node": "1.2.1", 43 | "tslint": "3.13.0", 44 | "typescript": "~2.0.3", 45 | "webdriver-manager": "10.2.5" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /site/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/docs/referenceConf.js 3 | 4 | /*global jasmine */ 5 | var SpecReporter = require('jasmine-spec-reporter'); 6 | 7 | exports.config = { 8 | allScriptsTimeout: 11000, 9 | specs: [ 10 | './e2e/**/*.e2e-spec.ts' 11 | ], 12 | capabilities: { 13 | 'browserName': 'chrome' 14 | }, 15 | directConnect: true, 16 | baseUrl: 'http://localhost:4200/', 17 | framework: 'jasmine', 18 | jasmineNodeOpts: { 19 | showColors: true, 20 | defaultTimeoutInterval: 30000, 21 | print: function() {} 22 | }, 23 | useAllAngular2AppRoots: true, 24 | beforeLaunch: function() { 25 | require('ts-node').register({ 26 | project: 'e2e' 27 | }); 28 | }, 29 | onPrepare: function() { 30 | jasmine.getEnv().addReporter(new SpecReporter()); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /site/src/app/app.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/app/app.component.css -------------------------------------------------------------------------------- /site/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 12 | My Blog 13 |
14 |
15 |
    16 |
  • Home
  • 17 |
  • Blog
  • 18 |
  • 19 | My account 21 |
      22 |
    • My profile
    • 23 |
    • Logout 24 | {{user.email}} 25 |
    • 26 |
    • Login
    • 27 |
    • Create an account
    • 28 |
    • Create blog post
    • 29 | 30 |
    31 |
  • 32 |
33 | 34 |
35 |
36 | 37 |
38 | 39 |
    40 |
  • 41 | {{post.title}} 42 |
  • 43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | 51 |
52 | 53 | 54 |
55 | -------------------------------------------------------------------------------- /site/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { AppComponent } from './app.component'; 5 | 6 | describe('App: Blog', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | declarations: [ 10 | AppComponent 11 | ], 12 | }); 13 | }); 14 | 15 | it('should create the app', async(() => { 16 | let fixture = TestBed.createComponent(AppComponent); 17 | let app = fixture.debugElement.componentInstance; 18 | expect(app).toBeTruthy(); 19 | })); 20 | 21 | it(`should have as title 'app works!'`, async(() => { 22 | let fixture = TestBed.createComponent(AppComponent); 23 | let app = fixture.debugElement.componentInstance; 24 | expect(app.title).toEqual('app works!'); 25 | })); 26 | 27 | it('should render title in a h1 tag', async(() => { 28 | let fixture = TestBed.createComponent(AppComponent); 29 | fixture.detectChanges(); 30 | let compiled = fixture.debugElement.nativeElement; 31 | expect(compiled.querySelector('h1').textContent).toContain('app works!'); 32 | })); 33 | }); 34 | -------------------------------------------------------------------------------- /site/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {User} from "./user/user"; 3 | import {AuthService} from "./user/auth.service"; 4 | import {isNullOrUndefined} from "util"; 5 | import {UserService} from "./user/user.service"; 6 | import {Router} from "@angular/router"; 7 | import {Subject} from "rxjs"; 8 | import {PostService} from "./blog/post.service"; 9 | import {Post} from "./blog/blog/post"; 10 | 11 | @Component({ 12 | selector: 'app-root', 13 | templateUrl: './app.component.html', 14 | styleUrls: ['./app.component.css'], 15 | providers: [PostService] 16 | }) 17 | export class AppComponent implements OnInit{ 18 | title = 'app works!'; 19 | 20 | user: User = new User(); 21 | loggedIn: boolean = false; 22 | 23 | private searchTerm = new Subject(); 24 | 25 | 26 | 27 | posts: Post[] = []; 28 | 29 | autocompleteBox = {hide: true}; 30 | 31 | 32 | constructor(private postService: PostService, private authService: AuthService, private userService: UserService, private router: Router) { 33 | 34 | this.user = this.authService.getCurrentUser(); 35 | if (this.user && !isNullOrUndefined(this.user)) { 36 | 37 | this.loggedIn = true; 38 | } 39 | 40 | 41 | this.searchTerm.debounceTime(200).distinctUntilChanged().subscribe(searchTerm => { 42 | 43 | 44 | this.postService.search(searchTerm).subscribe(response => { 45 | 46 | this.posts = response as Post[]; 47 | 48 | this.autocompleteBox.hide = false; 49 | 50 | }, err => { 51 | 52 | console.log(err); 53 | 54 | }); 55 | 56 | 57 | }); 58 | 59 | 60 | 61 | 62 | 63 | } 64 | 65 | ngOnInit(){ 66 | 67 | this.authService.onAuthChange$.subscribe(user => { 68 | if(user){ 69 | // this mean user has logged in. 70 | this.loggedIn = true; 71 | this.user = user; 72 | }else{ 73 | // user has logged out. 74 | this.loggedIn = false; 75 | } 76 | 77 | }); 78 | 79 | } 80 | 81 | 82 | logout() { 83 | this.loggedIn = false; 84 | this.userService.logout(); 85 | this.authService.logout(); 86 | 87 | //direct after loggout to the homepage. 88 | 89 | this.router.navigate(['/home']); 90 | 91 | 92 | } 93 | 94 | 95 | onKeyup(searchText: string){ 96 | 97 | if(searchText !== ""){ 98 | this.searchTerm.next(searchText); 99 | } 100 | 101 | } 102 | 103 | showDetail(post: Post){ 104 | 105 | this.autocompleteBox.hide = true; 106 | 107 | this.router.navigate(['/blog', post.id]); 108 | 109 | 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /site/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import {BrowserModule} from '@angular/platform-browser'; 2 | import {NgModule} from '@angular/core'; 3 | import {FormsModule} from '@angular/forms'; 4 | import {HttpModule} from '@angular/http'; 5 | 6 | import {AppComponent} from './app.component'; 7 | import {BlogComponent} from './blog/blog/blog.component'; 8 | import {Routes, RouterModule} from "@angular/router"; 9 | import {HomeComponent} from './home/home.component'; 10 | import {PostDetailComponent} from './blog/post-detail/post-detail.component'; 11 | import { PostFormComponent } from './blog/post-form/post-form.component'; 12 | import { LoginComponent } from './user/login/login.component'; 13 | import {AuthService} from "./user/auth.service"; 14 | import { ProfileComponent } from './user/profile/profile.component'; 15 | import {UserService} from "./user/user.service"; 16 | import { RegisterComponent } from './user/register/register.component'; 17 | import { UserPostsComponent } from './user/user-posts/user-posts.component'; 18 | import { EditorComponent } from './helpers/editor/editor.component'; 19 | import { CategoryViewComponent } from './blog/category-view/category-view.component'; 20 | import { CommentFormComponent } from './blog/comment/comment-form/comment-form.component'; 21 | 22 | 23 | const appRoutes: Routes = [ 24 | {path: 'blog', component: BlogComponent}, 25 | {path: 'blog/:id', component: PostDetailComponent}, 26 | {path: 'blog/:id/edit', component: PostFormComponent}, 27 | {path: 'blog-add', component: PostFormComponent}, 28 | {path: '', component: HomeComponent}, 29 | {path: 'home', component: HomeComponent}, 30 | {path: 'user/login', component: LoginComponent}, 31 | {path: 'user/my-account', component: ProfileComponent}, 32 | {path: 'user/register', component: RegisterComponent}, 33 | {path: 'user/:id/blog', component: UserPostsComponent}, 34 | {path: 'category/:id', component: CategoryViewComponent} 35 | 36 | 37 | ]; 38 | 39 | 40 | @NgModule({ 41 | declarations: [ 42 | AppComponent, 43 | BlogComponent, 44 | HomeComponent, 45 | PostDetailComponent, 46 | PostFormComponent, 47 | LoginComponent, 48 | ProfileComponent, 49 | RegisterComponent, 50 | UserPostsComponent, 51 | EditorComponent, 52 | CategoryViewComponent, 53 | CommentFormComponent 54 | ], 55 | imports: [ 56 | BrowserModule, 57 | FormsModule, 58 | HttpModule, 59 | RouterModule.forRoot(appRoutes) 60 | ], 61 | providers: [AuthService, UserService], 62 | bootstrap: [AppComponent] 63 | }) 64 | export class AppModule { 65 | } 66 | -------------------------------------------------------------------------------- /site/src/app/blog/blog/blog.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/app/blog/blog/blog.component.css -------------------------------------------------------------------------------- /site/src/app/blog/blog/blog.component.html: -------------------------------------------------------------------------------- 1 |

{{title}}

2 | 3 |
4 | 5 |
6 |
7 |
8 |

{{post.title}}

9 |
{{post.body}}
10 |
11 |
12 | 13 | 14 |
15 | 16 | 17 | Load more... 18 | Loading more... 19 | 20 | You're reached end of the list. 21 |
22 |
23 | 24 |
25 | 26 |
27 | 28 |
29 |

Categories

30 |
    31 |
  • {{cat.title}}
  • 32 |
33 |
34 | 35 | 36 | 37 |
38 |
39 | 40 |
41 | -------------------------------------------------------------------------------- /site/src/app/blog/blog/blog.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { BlogComponent } from './blog.component'; 7 | 8 | describe('BlogComponent', () => { 9 | let component: BlogComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ BlogComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(BlogComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /site/src/app/blog/blog/blog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import {PostService} from "../post.service"; 3 | import {Post} from "./post"; 4 | import {isNullOrUndefined} from "util"; 5 | import {Category} from "../category.model"; 6 | 7 | @Component({ 8 | selector: 'app-blog', 9 | templateUrl: './blog.component.html', 10 | styleUrls: ['./blog.component.css'], 11 | providers: [PostService] 12 | }) 13 | export class BlogComponent implements OnInit { 14 | 15 | 16 | title: string = "Blog"; 17 | posts: Post[] = []; 18 | 19 | categories: Category[] = []; 20 | 21 | pager = { 22 | limit: 2, // this is default number of posts load one time. 23 | current: 0, // current page. 24 | reachedEnd: false, 25 | isLoading: false, 26 | }; 27 | 28 | query = { 29 | limit: this.pager.limit, 30 | skip: this.pager.limit * this.pager.current 31 | }; 32 | 33 | constructor(private postService: PostService) { } 34 | 35 | ngOnInit() { 36 | 37 | // do request and get all blog entries 38 | 39 | this.getAll(); 40 | 41 | // get categories 42 | 43 | this.postService.getCategories().subscribe(res => { 44 | 45 | this.categories = res as Category[]; 46 | 47 | 48 | 49 | }, err => { 50 | 51 | console.log(err); 52 | }); 53 | 54 | 55 | } 56 | 57 | 58 | getAll(){ 59 | 60 | 61 | this.query.limit = this.pager.limit; 62 | this.query.skip = this.pager.limit * this.pager.current; 63 | 64 | let filter = encodeURI(JSON.stringify(this.query)); 65 | 66 | this.postService.getPosts(filter).subscribe(res => { 67 | 68 | // stop loading icon of pager 69 | 70 | this.pager.isLoading = false; 71 | 72 | if(!isNullOrUndefined(res) && res.length){ 73 | // we have posts here 74 | 75 | this.posts = this.posts.concat(res); 76 | }else{ 77 | 78 | this.pager.reachedEnd = true; 79 | 80 | } 81 | 82 | // now we detect if user has been reached to the end of the list. 83 | 84 | }, err => { 85 | 86 | // as well if detech error we also stop loading icon . 87 | 88 | this.pager.isLoading = false; 89 | 90 | console.log(err); 91 | }) 92 | 93 | 94 | } 95 | loadMore(){ 96 | 97 | // when click load more we need increase the current + 1 and fetch the blog posts 98 | // if current page 0. we skip: = limit * current 99 | this.pager.isLoading = true; 100 | this.pager.current = this.pager.current + 1; 101 | this.getAll(); 102 | 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /site/src/app/blog/blog/post.ts: -------------------------------------------------------------------------------- 1 | export class Post{ 2 | 3 | constructor( 4 | 5 | public id?: string, 6 | public title?: string, 7 | public body?: string, 8 | public categoryId?: string, 9 | public comments?: Comment[], 10 | 11 | ){ 12 | 13 | 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /site/src/app/blog/category-view/category-view.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/app/blog/category-view/category-view.component.css -------------------------------------------------------------------------------- /site/src/app/blog/category-view/category-view.component.html: -------------------------------------------------------------------------------- 1 |

{{category.title}}

2 | 3 |
4 | 5 |
6 |

{{post.title}}

7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /site/src/app/blog/category-view/category-view.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { CategoryViewComponent } from './category-view.component'; 7 | 8 | describe('CategoryViewComponent', () => { 9 | let component: CategoryViewComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ CategoryViewComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(CategoryViewComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /site/src/app/blog/category-view/category-view.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import {Post} from "../blog/post"; 3 | import {ActivatedRoute} from "@angular/router"; 4 | import {PostService} from "../post.service"; 5 | import {Category} from "../category.model"; 6 | 7 | @Component({ 8 | selector: 'app-category-view', 9 | templateUrl: './category-view.component.html', 10 | styleUrls: ['./category-view.component.css'] 11 | }) 12 | export class CategoryViewComponent implements OnInit { 13 | 14 | posts: Post[] = []; 15 | 16 | category: Category = new Category() 17 | 18 | constructor(private route: ActivatedRoute, private postService: PostService) { } 19 | 20 | ngOnInit() { 21 | 22 | 23 | let categoryId = this.route.snapshot.params["id"]; 24 | 25 | 26 | let query = { 27 | include: ["posts"] 28 | }; 29 | 30 | let filter = encodeURI(JSON.stringify(query)); 31 | 32 | this.postService.getCategoryById(categoryId, filter).subscribe(res => { 33 | 34 | this.category = res; 35 | 36 | this.posts = res.posts; 37 | 38 | }); 39 | 40 | 41 | 42 | 43 | 44 | 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /site/src/app/blog/category.model.ts: -------------------------------------------------------------------------------- 1 | import {Post} from "./blog/post"; 2 | 3 | export class Category{ 4 | 5 | 6 | constructor( 7 | public id?: string, 8 | public title?: string, 9 | public description?: string, 10 | public posts?: Post[], 11 | 12 | ){ 13 | 14 | 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /site/src/app/blog/comment/comment-form/comment-form.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/app/blog/comment/comment-form/comment-form.component.css -------------------------------------------------------------------------------- /site/src/app/blog/comment/comment-form/comment-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
{{errorMessage}}
3 | 4 | 5 |
6 | 7 | 9 |
10 | 11 | 12 |
13 | 14 | 16 |
17 | 18 |
19 | 20 | 23 | 25 |
26 | 27 | 28 |
29 | -------------------------------------------------------------------------------- /site/src/app/blog/comment/comment-form/comment-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { CommentFormComponent } from './comment-form.component'; 7 | 8 | describe('CommentFormComponent', () => { 9 | let component: CommentFormComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ CommentFormComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(CommentFormComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /site/src/app/blog/comment/comment-form/comment-form.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core'; 2 | import {Comment} from "../comment"; 3 | import {PostService} from "../../post.service"; 4 | 5 | @Component({ 6 | selector: 'app-comment-form', 7 | templateUrl: './comment-form.component.html', 8 | styleUrls: ['./comment-form.component.css'] 9 | }) 10 | export class CommentFormComponent implements OnInit { 11 | 12 | 13 | 14 | comment: Comment = new Comment; 15 | 16 | errorMessage: string = ""; 17 | 18 | @Input() postId: string = ""; 19 | @Output() newComment: EventEmitter = new EventEmitter(); 20 | 21 | 22 | 23 | constructor(private postService: PostService) { } 24 | 25 | 26 | 27 | 28 | ngOnInit() { 29 | 30 | 31 | this.comment.postId = this.postId; 32 | 33 | } 34 | 35 | 36 | onSubmit(){ 37 | 38 | this.postService.addComment(this.comment).subscribe(res => { 39 | 40 | // do later insert comment to the list. 41 | this.newComment.emit(res as Comment); 42 | 43 | 44 | }, err => { 45 | this.errorMessage = "An error saving the comment. Try again!"; 46 | console.log(err); 47 | 48 | }) 49 | 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /site/src/app/blog/comment/comment.ts: -------------------------------------------------------------------------------- 1 | export class Comment{ 2 | 3 | constructor( 4 | public name?: string, 5 | public title?: string, 6 | public body?: string, 7 | public postId?: string, 8 | 9 | ){ 10 | 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /site/src/app/blog/post-detail/post-detail.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/app/blog/post-detail/post-detail.component.css -------------------------------------------------------------------------------- /site/src/app/blog/post-detail/post-detail.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • View
  • 3 |
  • Edit
  • 4 |
  • Delete
  • 5 |
6 | 7 | 8 |
9 |

{{post.title}}

10 |
11 |
12 | 13 | 14 |
15 |

Comments:

16 |
    17 |
  • 18 |

    {{comment.name}} says: {{comment.title}}

    19 |
    {{comment.body}}
    20 |
  • 21 |
22 |
23 | 24 |
25 |

Write your comment:

26 | 27 |
28 | -------------------------------------------------------------------------------- /site/src/app/blog/post-detail/post-detail.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { PostDetailComponent } from './post-detail.component'; 7 | 8 | describe('PostDetailComponent', () => { 9 | let component: PostDetailComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ PostDetailComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(PostDetailComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /site/src/app/blog/post-detail/post-detail.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {ActivatedRoute, Params} from "@angular/router"; 3 | import {PostService} from "../post.service"; 4 | import {Post} from "../blog/post"; 5 | import {Comment} from "../comment/comment"; 6 | 7 | @Component({ 8 | selector: 'app-post-detail', 9 | templateUrl: './post-detail.component.html', 10 | styleUrls: ['./post-detail.component.css'], 11 | providers: [PostService] 12 | }) 13 | export class PostDetailComponent implements OnInit { 14 | 15 | 16 | post: Post = new Post(); 17 | comments: Comment[] = []; 18 | postId: string = ""; 19 | 20 | 21 | 22 | constructor(private route: ActivatedRoute, 23 | protected postService: PostService) { 24 | } 25 | 26 | ngOnInit() { 27 | 28 | 29 | this.route.params.switchMap((params: Params) => { 30 | 31 | let id = params['id']; 32 | 33 | this.postId = id; 34 | 35 | let query = { 36 | include: ["comments"] 37 | }; 38 | let filter = encodeURI(JSON.stringify(query)); 39 | 40 | return this.postService.getPost(id, filter); 41 | }).subscribe(response => { 42 | 43 | this.post = response; 44 | this.comments = response.comments; 45 | 46 | 47 | }, err => { 48 | 49 | console.log(err); 50 | }); 51 | 52 | } 53 | 54 | 55 | 56 | 57 | 58 | onNewComment(event){ 59 | 60 | this.comments.push(event); 61 | 62 | 63 | } 64 | 65 | 66 | 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /site/src/app/blog/post-form/post-form.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/app/blog/post-form/post-form.component.css -------------------------------------------------------------------------------- /site/src/app/blog/post-form/post-form.component.html: -------------------------------------------------------------------------------- 1 |

{{post.title}}Create new post

2 |
3 |
    4 |
  • View
  • 5 |
  • Edit
  • 6 |
  • Delete
  • 7 |
  • Create new post
  • 8 |
9 |
10 | 11 | 12 |
13 |
{{errorMessage}}
14 |
15 | 16 | 17 |
18 | 19 |
20 | 21 | 22 | 25 |
26 | 27 |
28 | 29 | 30 |
31 | 32 | 33 |
34 | -------------------------------------------------------------------------------- /site/src/app/blog/post-form/post-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { PostFormComponent } from './post-form.component'; 7 | 8 | describe('PostFormComponent', () => { 9 | let component: PostFormComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ PostFormComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(PostFormComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /site/src/app/blog/post-form/post-form.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {Post} from "../blog/post"; 3 | import {PostService} from "../post.service"; 4 | import {Router, ActivatedRoute, Params} from "@angular/router"; 5 | import post = http.post; 6 | import {Observable} from "rxjs"; 7 | import {Category} from "../category.model"; // this thanks to Webstorm :) 8 | 9 | @Component({ 10 | selector: 'app-post-form', 11 | templateUrl: './post-form.component.html', 12 | styleUrls: ['./post-form.component.css'], 13 | providers: [PostService] 14 | }) 15 | export class PostFormComponent implements OnInit { 16 | 17 | 18 | post: Post = new Post(); 19 | 20 | errorMessage = ""; 21 | loading = false; 22 | defaultBodyValue: string = ""; 23 | 24 | categories: Category[] = []; 25 | 26 | 27 | constructor(private postService: PostService, 28 | private router: Router, 29 | private route: ActivatedRoute) { 30 | } 31 | 32 | ngOnInit() { 33 | 34 | 35 | if (this.route.snapshot.params['id']) { 36 | this.route.params.switchMap((params: Params) => { 37 | 38 | let id = params['id']; 39 | if (typeof params['id'] !== "undefined" && params['id'] !== null) { 40 | 41 | 42 | this.loading = true; 43 | return this.postService.getPost(id); // we can see an error if params["id"] is undefined or null. let check.. 44 | } 45 | 46 | 47 | }).subscribe(res => { 48 | 49 | // after get the post detail we set loading to false. 50 | this.loading = false; 51 | this.post = res as Post; // if post is being edit. we get the id from params , and get detail of the post via postService. 52 | 53 | this.defaultBodyValue = this.post.body; 54 | 55 | 56 | }, err => { 57 | 58 | console.log(err); 59 | }); 60 | } 61 | 62 | 63 | this.postService.getCategories().subscribe(res => { 64 | 65 | this.categories = res; 66 | 67 | }, err => { 68 | 69 | console.log(err); 70 | 71 | }); 72 | 73 | 74 | } 75 | 76 | onSubmit() { 77 | 78 | 79 | // if the post.id is not null that mean we need update the post. otherwise create new post 80 | 81 | if (this.post.id) { 82 | 83 | // do save the post 84 | 85 | this.postService.updatePost(this.post).subscribe(res => { 86 | 87 | // this mean the post has been saved 88 | // now we can redirect to the post view. 89 | this.router.navigate(['/blog', this.post.id]); 90 | 91 | }, err => { 92 | 93 | console.log(err); // this for development only. 94 | this.errorMessage = "An error saving the post."; 95 | }); 96 | 97 | 98 | } else { 99 | 100 | // let do post this data to rest service... 101 | 102 | this.postService.createPost(this.post).subscribe(res => { 103 | 104 | // we got successful the post 105 | console.log(res.id); // this is post ID we can use to redirect to view the detail of the post. 106 | 107 | // direct to view post 108 | 109 | this.router.navigate(['/blog', res.id]); 110 | 111 | }, err => { 112 | 113 | console.log(err); 114 | this.errorMessage = "An error saving the post."; 115 | }); 116 | 117 | 118 | } 119 | 120 | 121 | } 122 | 123 | 124 | onBodyTextEditorKeyUp(textValue) { 125 | 126 | this.post.body = textValue; 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /site/src/app/blog/post.service.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async, inject } from '@angular/core/testing'; 4 | import { PostService } from './post.service'; 5 | 6 | describe('Service: Post', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | providers: [PostService] 10 | }); 11 | }); 12 | 13 | it('should ...', inject([PostService], (service: PostService) => { 14 | expect(service).toBeTruthy(); 15 | })); 16 | }); 17 | -------------------------------------------------------------------------------- /site/src/app/blog/post.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {Http, Headers} from "@angular/http"; 3 | import {Observable} from "rxjs"; 4 | import {Post} from "./blog/post"; 5 | import {isNull} from "util"; 6 | import {AuthService} from "../user/auth.service"; 7 | import {User} from "../user/user"; 8 | import {Category} from "./category.model"; 9 | import {Comment} from "./comment/comment"; 10 | 11 | @Injectable() 12 | export class PostService { 13 | 14 | 15 | serverUrl = "http://0.0.0.0:3000/api"; 16 | 17 | 18 | constructor(private http: Http, private authService: AuthService) { 19 | } 20 | 21 | 22 | headers = new Headers({ 23 | 24 | 'Content-Type': 'application/json', 25 | 'Authorization': this.authService.getToken(), 26 | }); 27 | 28 | 29 | updateHeaders() { 30 | 31 | this.headers.set('Authorization', this.authService.getToken()); 32 | } 33 | 34 | getPosts(filter: string): Observable { 35 | 36 | // in the part we get all the post without filter query, now we need pass the filter 37 | let url = this.serverUrl + "/posts"; 38 | 39 | if (!isNull(filter) && filter !== "") { 40 | url = url + '?filter=' + filter; 41 | } 42 | return this.http.get(url, {headers: this.headers}).map(res => res.json()).catch(err => { 43 | 44 | return Observable.throw(err); 45 | }); 46 | } 47 | 48 | search(text: string): Observable { 49 | 50 | let query = { 51 | where: { 52 | or: [{title: {like: text, options: "i"}}], 53 | } 54 | }; 55 | 56 | let filter = encodeURI(JSON.stringify(query)); 57 | 58 | let url = this.serverUrl + "/posts?filter=" + filter; 59 | 60 | return this.http.get(url, {headers: this.headers}).map(res => res.json()).catch(err => { 61 | 62 | return Observable.throw(err); 63 | }); 64 | 65 | 66 | } 67 | 68 | 69 | addComment(comment: Comment): Observable{ 70 | 71 | let url = this.serverUrl + '/posts/'+comment.postId+'/comments'; 72 | return this.http.post(url, comment, {headers: this.headers}).map(res => res.json()).catch(err => Observable.throw(err)); 73 | } 74 | 75 | getPost(id: string, filter?: string): Observable { 76 | let url = this.serverUrl + "/posts/" + id; 77 | if(filter){ 78 | url = this.serverUrl + "/posts/" + id + "?filter=" + filter; 79 | } 80 | return this.http.get(url, {headers: this.headers}).map(res => res.json() as Post).catch(err => { 81 | 82 | return Observable.throw(err); 83 | }); 84 | } 85 | 86 | getCategories(): Observable { 87 | 88 | let url = this.serverUrl + '/categories'; 89 | 90 | return this.http.get(url, {headers: this.headers}).map(res => res.json() as Category[]).catch(err => Observable.throw(err)); 91 | } 92 | 93 | getCategoryById(id: string, filter?: string): Observable { 94 | 95 | let url = this.serverUrl + '/categories/' + id + '?filter=' + filter; 96 | return this.http.get(url, {headers: this.headers}).map(res => res.json() as Category).catch(err => Observable.throw(err)); 97 | } 98 | 99 | getPostByCategoryId(id: string): Observable { 100 | 101 | 102 | let url = this.serverUrl + '/categories/' + id + '/posts'; 103 | 104 | return this.http.get(url, {headers: this.headers}).map(res => res.json() as Post[]).catch(err => Observable.throw(err)); 105 | } 106 | 107 | getUserPosts(userId: string, filter: string) { 108 | 109 | 110 | // in the part we get all the post without filter query, now we need pass the filter 111 | let url = this.serverUrl + "/accounts/" + userId + "/posts"; 112 | 113 | if (!isNull(filter) && filter !== "") { 114 | url = url + '?filter=' + filter; 115 | } 116 | return this.http.get(url, {headers: this.headers}).map(res => res.json()).catch(err => { 117 | 118 | return Observable.throw(err); 119 | }); 120 | 121 | } 122 | 123 | 124 | createPost(post: Post): Observable { 125 | 126 | 127 | this.updateHeaders(); 128 | 129 | let user = this.authService.getCurrentUser() as User; 130 | let userId = user.id; 131 | let url = this.serverUrl + "/accounts/" + userId + "/posts"; 132 | return this.http.post(url, post, {headers: this.headers}).map(res => res.json()).catch(err => { 133 | 134 | return Observable.throw(err); 135 | }) 136 | } 137 | 138 | updatePost(post: Post): Observable { 139 | 140 | this.updateHeaders(); 141 | 142 | let url = this.serverUrl + "/posts/" + post.id 143 | return this.http.put(url, post, {headers: this.headers}).map(res => res.json()).catch(err => { 144 | return Observable.throw(err); 145 | }) 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /site/src/app/helpers/editor/editor.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/app/helpers/editor/editor.component.css -------------------------------------------------------------------------------- /site/src/app/helpers/editor/editor.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /site/src/app/helpers/editor/editor.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { EditorComponent } from './editor.component'; 7 | 8 | describe('EditorComponent', () => { 9 | let component: EditorComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ EditorComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(EditorComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /site/src/app/helpers/editor/editor.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnDestroy, AfterViewInit, EventEmitter, Input, Output, OnInit, OnChanges} from '@angular/core'; 2 | import {isNullOrUndefined} from "util"; 3 | 4 | 5 | @Component({ 6 | selector: 'text-editor', 7 | templateUrl: './editor.component.html', 8 | styleUrls: ['./editor.component.css'] 9 | }) 10 | export class EditorComponent implements OnInit, AfterViewInit, OnDestroy, OnChanges { 11 | 12 | 13 | @Input() elementId: string; 14 | @Input() value: any = ""; 15 | @Output() onEditorKeyup: EventEmitter = new EventEmitter(); 16 | 17 | baseURL: string = '/'; 18 | 19 | constructor() { 20 | } 21 | 22 | 23 | ngOnInit() { 24 | 25 | 26 | } 27 | 28 | 29 | editor; 30 | 31 | ngAfterViewInit() { 32 | tinymce.init({ 33 | selector: '#' + this.elementId, 34 | plugins: ['link', 'paste', 'table'], 35 | skin_url: this.baseURL + 'assets/skins/lightgray', 36 | setup: editor => { 37 | this.editor = editor; 38 | editor.on('keyup', () => { 39 | const content = editor.getContent(); 40 | this.onEditorKeyup.emit(content); 41 | }); 42 | }, 43 | }); 44 | } 45 | 46 | ngOnDestroy() { 47 | tinymce.remove(this.editor); 48 | } 49 | 50 | 51 | didSetValue: boolean = false; 52 | 53 | 54 | ngOnChanges(){ 55 | 56 | console.log(this.value); 57 | 58 | if(!isNullOrUndefined(this.editor) && this.value !== "" && !this.didSetValue){ 59 | 60 | console.log(this.value); 61 | this.didSetValue = true; 62 | this.editor.setContent(this.value); 63 | 64 | 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /site/src/app/home/home.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/app/home/home.component.css -------------------------------------------------------------------------------- /site/src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 |

{{title}}

2 |

3 | Welcome to Home page. 4 |

5 | -------------------------------------------------------------------------------- /site/src/app/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { HomeComponent } from './home.component'; 7 | 8 | describe('HomeComponent', () => { 9 | let component: HomeComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ HomeComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(HomeComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /site/src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.component.css'] 7 | }) 8 | export class HomeComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | title: string = "Home"; 13 | 14 | ngOnInit() { 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /site/src/app/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app.component'; 2 | export * from './app.module'; 3 | -------------------------------------------------------------------------------- /site/src/app/user/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async, inject } from '@angular/core/testing'; 4 | import { AuthService } from './auth.service'; 5 | 6 | describe('Service: Auth', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | providers: [AuthService] 10 | }); 11 | }); 12 | 13 | it('should ...', inject([AuthService], (service: AuthService) => { 14 | expect(service).toBeTruthy(); 15 | })); 16 | }); 17 | -------------------------------------------------------------------------------- /site/src/app/user/auth.service.ts: -------------------------------------------------------------------------------- 1 | import {Injectable} from '@angular/core'; 2 | import {User} from "./user"; 3 | import {isNullOrUndefined} from "util"; 4 | import {Observable, Subject} from "rxjs"; 5 | 6 | @Injectable() 7 | export class AuthService { 8 | 9 | 10 | 11 | public onAuthChange$: Subject; 12 | constructor() { 13 | 14 | 15 | this.onAuthChange$ = new Subject(); 16 | } 17 | 18 | 19 | 20 | setUser(user: User) { 21 | 22 | 23 | 24 | this.onAuthChange$.next(user); 25 | 26 | let userString = JSON.stringify(user); 27 | localStorage.setItem("currentUser", userString); 28 | 29 | } 30 | 31 | getCurrentUser(): User { 32 | 33 | let userString = localStorage.getItem("currentUser"); 34 | if (!isNullOrUndefined(userString)) { 35 | let user: User = JSON.parse(userString); 36 | 37 | return user; 38 | } else { 39 | 40 | return null; 41 | } 42 | } 43 | 44 | setToken(token: string) { 45 | 46 | localStorage.setItem("accessToken", token); 47 | } 48 | 49 | getToken(): string { 50 | 51 | return localStorage.getItem("accessToken"); 52 | } 53 | 54 | logout(){ 55 | 56 | this.onAuthChange$.next(null); 57 | // we need also request logout to the server api 58 | 59 | localStorage.removeItem("currentUser"); 60 | localStorage.removeItem("accessToken"); 61 | 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /site/src/app/user/login/login.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/app/user/login/login.component.css -------------------------------------------------------------------------------- /site/src/app/user/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 6 |
7 |
8 | 9 | 10 |
11 | 12 | 13 |
14 | -------------------------------------------------------------------------------- /site/src/app/user/login/login.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { LoginComponent } from './login.component'; 7 | 8 | describe('LoginComponent', () => { 9 | let component: LoginComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ LoginComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(LoginComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /site/src/app/user/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {User} from "../user"; 3 | import {UserService} from "../user.service"; 4 | import {AuthService} from "../auth.service"; 5 | import {Router} from "@angular/router"; 6 | 7 | @Component({ 8 | selector: 'app-login', 9 | templateUrl: './login.component.html', 10 | styleUrls: ['./login.component.css'], 11 | providers: [UserService] 12 | }) 13 | export class LoginComponent implements OnInit { 14 | 15 | 16 | user: User = new User(); 17 | 18 | constructor( 19 | private userService: UserService, 20 | private authService: AuthService, 21 | private router: Router 22 | ) { 23 | } 24 | 25 | ngOnInit() { 26 | } 27 | 28 | 29 | onLogin() { 30 | 31 | console.log("login tapped with data: ", this.user); 32 | 33 | // we have uuser login data. now post to the api server. to verify account. and get the access token key. 34 | 35 | // generate new service. 36 | 37 | let user = this.user; 38 | this.userService.login(user.username, user.password).subscribe(response => { 39 | 40 | console.log(response); 41 | // now we need save user token to the cookie. or now i just simply save to Local storage. 42 | 43 | let user = response.user; 44 | this.authService.setUser(user); 45 | 46 | let token = response.id; 47 | 48 | this.authService.setToken(token); 49 | 50 | // now we need redirect to profile user if they logged in. 51 | 52 | this.router.navigate(['/user/my-account']); 53 | 54 | 55 | }, err => { 56 | 57 | console.log(err); 58 | }) 59 | 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /site/src/app/user/profile/profile.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/app/user/profile/profile.component.css -------------------------------------------------------------------------------- /site/src/app/user/profile/profile.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • My account
  • 3 |
  • My posts
  • 4 |
5 | 6 | 7 | 8 |
9 |

Welcome: {{user.username}}

10 |
11 |
    12 |
  • First Name: {{user.firstName}}
  • 13 |
  • Last Name: {{user.lastName}}
  • 14 |
  • email: {{user.email}}
  • 15 |
16 |
17 | 18 | 19 |
20 | -------------------------------------------------------------------------------- /site/src/app/user/profile/profile.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { ProfileComponent } from './profile.component'; 7 | 8 | describe('ProfileComponent', () => { 9 | let component: ProfileComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ ProfileComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(ProfileComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /site/src/app/user/profile/profile.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import {User} from "../user"; 3 | import {AuthService} from "../auth.service"; 4 | import {Router} from "@angular/router"; 5 | 6 | @Component({ 7 | selector: 'app-profile', 8 | templateUrl: './profile.component.html', 9 | styleUrls: ['./profile.component.css'] 10 | }) 11 | export class ProfileComponent implements OnInit { 12 | 13 | 14 | user: User = new User(); 15 | constructor(private authService: AuthService, private router: Router) { } 16 | 17 | ngOnInit() { 18 | this.user = this.authService.getCurrentUser(); 19 | 20 | if(this.user === null){ 21 | 22 | // redirect to the login page 23 | this.router.navigate(['/user/login']); 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /site/src/app/user/register/register.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/app/user/register/register.component.css -------------------------------------------------------------------------------- /site/src/app/user/register/register.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | 7 |
8 | 9 | 10 |
11 | 12 | 13 |
14 | 15 | 16 |
17 | 18 |
19 | 20 | 21 |
22 |
23 | 24 | 25 |
26 | 27 |
28 | -------------------------------------------------------------------------------- /site/src/app/user/register/register.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { RegisterComponent } from './register.component'; 7 | 8 | describe('RegisterComponent', () => { 9 | let component: RegisterComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ RegisterComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(RegisterComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /site/src/app/user/register/register.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import {User} from "../user"; 3 | import {UserService} from "../user.service"; 4 | import {Router} from "@angular/router"; 5 | 6 | @Component({ 7 | selector: 'app-register', 8 | templateUrl: './register.component.html', 9 | styleUrls: ['./register.component.css'] 10 | }) 11 | export class RegisterComponent implements OnInit { 12 | 13 | 14 | user: User = new User(); 15 | 16 | 17 | constructor(private userService: UserService, private router: Router) { } 18 | 19 | ngOnInit() { 20 | 21 | } 22 | 23 | 24 | onRegister(){ 25 | 26 | 27 | this.userService.register(this.user).subscribe(res => { 28 | 29 | console.log("An account has been created with information: ", res); 30 | 31 | this.router.navigate(['/user/login']); 32 | 33 | }, err => { 34 | 35 | console.log(err); 36 | }); 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /site/src/app/user/user-posts/user-posts.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/app/user/user-posts/user-posts.component.css -------------------------------------------------------------------------------- /site/src/app/user/user-posts/user-posts.component.html: -------------------------------------------------------------------------------- 1 |

{{pageTitle}}

2 |
    3 |
  • My account
  • 4 |
  • My posts
  • 5 |
6 | 7 |
8 |

Welsome to {{user.firstName}}' Blog.

9 | 10 |
11 |

{{post.title}}

12 |
13 | Author: {{post.account.username}} 14 |
15 |
{{post.body}}
16 |
17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /site/src/app/user/user-posts/user-posts.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { DebugElement } from '@angular/core'; 5 | 6 | import { UserPostsComponent } from './user-posts.component'; 7 | 8 | describe('UserPostsComponent', () => { 9 | let component: UserPostsComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async(() => { 13 | TestBed.configureTestingModule({ 14 | declarations: [ UserPostsComponent ] 15 | }) 16 | .compileComponents(); 17 | })); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(UserPostsComponent); 21 | component = fixture.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it('should create', () => { 26 | expect(component).toBeTruthy(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /site/src/app/user/user-posts/user-posts.component.ts: -------------------------------------------------------------------------------- 1 | import {Component, OnInit} from '@angular/core'; 2 | import {User} from "../user"; 3 | import {AuthService} from "../auth.service"; 4 | import {PostService} from "../../blog/post.service"; 5 | import {ActivatedRoute} from "@angular/router"; 6 | import {Post} from "../../blog/blog/post"; 7 | import {UserService} from "../user.service"; 8 | import {Title} from "@angular/platform-browser"; 9 | 10 | @Component({ 11 | selector: 'app-user-posts', 12 | templateUrl: './user-posts.component.html', 13 | styleUrls: ['./user-posts.component.css'], 14 | providers: [PostService] 15 | }) 16 | export class UserPostsComponent implements OnInit { 17 | 18 | user: User = new User(); 19 | pageTitle: string = ""; 20 | posts: Post[] = []; 21 | 22 | constructor(private title: Title, private userService: UserService, private authService: AuthService, private postService: PostService, private route: ActivatedRoute) { 23 | 24 | 25 | 26 | 27 | } 28 | 29 | ngOnInit() { 30 | 31 | 32 | var userId = this.route.snapshot.params['id']; 33 | 34 | this.user = this.userService.getUserById(userId).subscribe(res => { 35 | 36 | this.user = res as User; 37 | 38 | let userBlogTitle = this.user.firstName + "' Blog"; 39 | this.pageTitle = userBlogTitle; 40 | 41 | 42 | this.title.setTitle(userBlogTitle); 43 | 44 | 45 | 46 | 47 | let query = { 48 | include: ["account"], 49 | } 50 | let filter = encodeURI(JSON.stringify(query)); 51 | 52 | this.postService.getUserPosts(userId, filter).subscribe(response => { 53 | 54 | console.log(response); 55 | this.posts = response as Post[]; 56 | 57 | }, err => { 58 | 59 | console.log(err); 60 | }); 61 | 62 | 63 | }, err => { 64 | 65 | console.log(err); 66 | }); 67 | 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /site/src/app/user/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async, inject } from '@angular/core/testing'; 4 | import { UserService } from './user.service'; 5 | 6 | describe('Service: User', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | providers: [UserService] 10 | }); 11 | }); 12 | 13 | it('should ...', inject([UserService], (service: UserService) => { 14 | expect(service).toBeTruthy(); 15 | })); 16 | }); 17 | -------------------------------------------------------------------------------- /site/src/app/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import {Http, Headers} from "@angular/http"; 3 | import {Observable} from "rxjs"; 4 | import {AuthService} from "./auth.service"; 5 | import {User} from "./user"; 6 | 7 | @Injectable() 8 | export class UserService { 9 | 10 | 11 | headers = new Headers({ 12 | "Content-Type": "application/json", 13 | 'Authorization': this.authService.getToken(), 14 | }); 15 | 16 | serverUrl = "http://0.0.0.0:3000/api"; 17 | 18 | constructor(private http:Http, private authService: AuthService) { 19 | 20 | 21 | 22 | } 23 | 24 | getUserById(id: string): Observable{ 25 | let url = this.serverUrl + "/accounts/" + id; 26 | return this.http.get(url, {headers: this.headers}).map(res => res.json()).catch(err => { 27 | 28 | return Observable.throw(err); 29 | }); 30 | } 31 | 32 | 33 | login(username: string, password: string): Observable{ 34 | 35 | let url = this.serverUrl + "/accounts/login?include=user"; 36 | 37 | return this.http.post(url, {username: username, password: password}, {headers: this.headers}).map(res => res.json()).catch(err => { 38 | 39 | return Observable.throw(err); 40 | }) 41 | } 42 | 43 | register(user: User): Observable{ 44 | let url = this.serverUrl + "/accounts"; 45 | this.headers.delete('Authorization'); 46 | 47 | return this.http.post(url, user, {headers: this.headers}).map(res => res.json()).catch(err => { 48 | 49 | return Observable.throw(err); 50 | }); 51 | } 52 | 53 | logout(): Observable{ 54 | 55 | let url = this.serverUrl + '/accounts/logout'; 56 | let data = {accessTokenID: this.authService.getToken()}; 57 | return this.http.post(url, data, {headers: this.headers}).map(res => res.json()).catch(err => { 58 | return Observable.throw(err); 59 | }); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /site/src/app/user/user.ts: -------------------------------------------------------------------------------- 1 | export class User { 2 | 3 | 4 | constructor(public id?: string, 5 | public firstName?: string, 6 | public lastName?: string, 7 | public username?: string, 8 | public email?: string, 9 | public password?: string,) { 10 | 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /site/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/assets/.gitkeep -------------------------------------------------------------------------------- /site/src/assets/skins/lightgray/content.inline.min.css: -------------------------------------------------------------------------------- 1 | .mce-content-body .mce-reset{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:black;font-family:Arial;font-size:11px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;line-height:normal;font-weight:normal;text-align:left;-webkit-tap-highlight-color:transparent;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;direction:ltr;max-width:none}.mce-object{border:1px dotted #3A3A3A;background:#D5D5D5 url(img/object.gif) no-repeat center}.mce-preview-object{display:inline-block;position:relative;margin:0 2px 0 2px;line-height:0;border:1px solid gray}.mce-preview-object .mce-shim{position:absolute;top:0;left:0;width:100%;height:100%;background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)}figure.align-left{float:left}figure.align-right{float:right}figure.image.align-center{display:table;margin-left:auto;margin-right:auto}figure.image{display:inline-block;border:1px solid gray;margin:0 2px 0 1px;background:#f5f2f0}figure.image img{margin:8px 8px 0 8px}figure.image figcaption{margin:6px 8px 6px 8px;text-align:center}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc li{list-style-type:none}.mce-preview-object[data-mce-selected="2"] .mce-shim{display:none}.mce-pagebreak{cursor:default;display:block;border:0;width:100%;height:5px;border:1px dashed #666;margin-top:15px;page-break-before:always}@media print{.mce-pagebreak{border:0}}.mce-item-anchor{cursor:default;display:inline-block;-webkit-user-select:all;-webkit-user-modify:read-only;-moz-user-select:all;-moz-user-modify:read-only;user-select:all;user-modify:read-only;width:9px !important;height:9px !important;border:1px dotted #3A3A3A;background:#D5D5D5 url(img/anchor.gif) no-repeat center}.mce-nbsp,.mce-shy{background:#AAA}.mce-shy::after{content:'-'}hr{cursor:default}.mce-match-marker{background:#AAA;color:#fff}.mce-match-marker-selected{background:#3399ff;color:#fff}.mce-spellchecker-word{border-bottom:2px solid #F00;cursor:default}.mce-spellchecker-grammar{border-bottom:2px solid #008000;cursor:default}.mce-item-table,.mce-item-table td,.mce-item-table th,.mce-item-table caption{border:1px dashed #BBB}td[data-mce-selected],th[data-mce-selected]{background-color:#3399ff !important}.mce-edit-focus{outline:1px dotted #333}.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus{outline:2px solid #2d8ac7}.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover{outline:2px solid #7ACAFF}.mce-content-body *[contentEditable=false][data-mce-selected]{outline:2px solid #2d8ac7}.mce-resize-bar-dragging{background-color:blue;opacity:.25;filter:alpha(opacity=25);zoom:1} -------------------------------------------------------------------------------- /site/src/assets/skins/lightgray/content.min.css: -------------------------------------------------------------------------------- 1 | body{background-color:#FFFFFF;color:#000000;font-family:Verdana,Arial,Helvetica,sans-serif;font-size:14px;scrollbar-3dlight-color:#F0F0EE;scrollbar-arrow-color:#676662;scrollbar-base-color:#F0F0EE;scrollbar-darkshadow-color:#DDDDDD;scrollbar-face-color:#E0E0DD;scrollbar-highlight-color:#F0F0EE;scrollbar-shadow-color:#F0F0EE;scrollbar-track-color:#F5F5F5}td,th{font-family:Verdana,Arial,Helvetica,sans-serif;font-size:14px}.mce-content-body .mce-reset{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:black;font-family:Arial;font-size:11px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;line-height:normal;font-weight:normal;text-align:left;-webkit-tap-highlight-color:transparent;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;direction:ltr;max-width:none}.mce-object{border:1px dotted #3A3A3A;background:#D5D5D5 url(img/object.gif) no-repeat center}.mce-preview-object{display:inline-block;position:relative;margin:0 2px 0 2px;line-height:0;border:1px solid gray}.mce-preview-object .mce-shim{position:absolute;top:0;left:0;width:100%;height:100%;background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)}figure.align-left{float:left}figure.align-right{float:right}figure.image.align-center{display:table;margin-left:auto;margin-right:auto}figure.image{display:inline-block;border:1px solid gray;margin:0 2px 0 1px;background:#f5f2f0}figure.image img{margin:8px 8px 0 8px}figure.image figcaption{margin:6px 8px 6px 8px;text-align:center}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc li{list-style-type:none}.mce-preview-object[data-mce-selected="2"] .mce-shim{display:none}.mce-pagebreak{cursor:default;display:block;border:0;width:100%;height:5px;border:1px dashed #666;margin-top:15px;page-break-before:always}@media print{.mce-pagebreak{border:0}}.mce-item-anchor{cursor:default;display:inline-block;-webkit-user-select:all;-webkit-user-modify:read-only;-moz-user-select:all;-moz-user-modify:read-only;user-select:all;user-modify:read-only;width:9px !important;height:9px !important;border:1px dotted #3A3A3A;background:#D5D5D5 url(img/anchor.gif) no-repeat center}.mce-nbsp,.mce-shy{background:#AAA}.mce-shy::after{content:'-'}hr{cursor:default}.mce-match-marker{background:#AAA;color:#fff}.mce-match-marker-selected{background:#3399ff;color:#fff}.mce-spellchecker-word{border-bottom:2px solid #F00;cursor:default}.mce-spellchecker-grammar{border-bottom:2px solid #008000;cursor:default}.mce-item-table,.mce-item-table td,.mce-item-table th,.mce-item-table caption{border:1px dashed #BBB}td[data-mce-selected],th[data-mce-selected]{background-color:#3399ff !important}.mce-edit-focus{outline:1px dotted #333}.mce-content-body *[contentEditable=false] *[contentEditable=true]:focus{outline:2px solid #2d8ac7}.mce-content-body *[contentEditable=false] *[contentEditable=true]:hover{outline:2px solid #7ACAFF}.mce-content-body *[contentEditable=false][data-mce-selected]{outline:2px solid #2d8ac7}.mce-resize-bar-dragging{background-color:blue;opacity:.25;filter:alpha(opacity=25);zoom:1} -------------------------------------------------------------------------------- /site/src/assets/skins/lightgray/fonts/tinymce-small.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/assets/skins/lightgray/fonts/tinymce-small.eot -------------------------------------------------------------------------------- /site/src/assets/skins/lightgray/fonts/tinymce-small.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /site/src/assets/skins/lightgray/fonts/tinymce-small.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/assets/skins/lightgray/fonts/tinymce-small.ttf -------------------------------------------------------------------------------- /site/src/assets/skins/lightgray/fonts/tinymce-small.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/assets/skins/lightgray/fonts/tinymce-small.woff -------------------------------------------------------------------------------- /site/src/assets/skins/lightgray/fonts/tinymce.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/assets/skins/lightgray/fonts/tinymce.eot -------------------------------------------------------------------------------- /site/src/assets/skins/lightgray/fonts/tinymce.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/assets/skins/lightgray/fonts/tinymce.ttf -------------------------------------------------------------------------------- /site/src/assets/skins/lightgray/fonts/tinymce.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/assets/skins/lightgray/fonts/tinymce.woff -------------------------------------------------------------------------------- /site/src/assets/skins/lightgray/img/anchor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/assets/skins/lightgray/img/anchor.gif -------------------------------------------------------------------------------- /site/src/assets/skins/lightgray/img/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/assets/skins/lightgray/img/loader.gif -------------------------------------------------------------------------------- /site/src/assets/skins/lightgray/img/object.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/assets/skins/lightgray/img/object.gif -------------------------------------------------------------------------------- /site/src/assets/skins/lightgray/img/trans.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/assets/skins/lightgray/img/trans.gif -------------------------------------------------------------------------------- /site/src/assets/skins/lightgray/skin.ie7.min.css: -------------------------------------------------------------------------------- 1 | .mce-container,.mce-container *,.mce-widget,.mce-widget *,.mce-reset{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:#333;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;-webkit-tap-highlight-color:transparent;line-height:normal;font-weight:normal;text-align:left;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;direction:ltr;max-width:none}.mce-widget button{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.mce-container *[unselectable]{-moz-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}.mce-fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.mce-fade.mce-in{opacity:1}.mce-tinymce{visibility:inherit !important;position:relative}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;height:100%;z-index:100}div.mce-fullscreen{position:fixed;top:0;left:0;width:100%;height:auto}.mce-tinymce{display:block}.mce-wordcount{position:absolute;top:0;right:0;padding:8px}div.mce-edit-area{background:#FFF;filter:none}.mce-statusbar{position:relative}.mce-statusbar .mce-container-body{position:relative}.mce-fullscreen .mce-resizehandle{display:none}.mce-charmap{border-collapse:collapse}.mce-charmap td{cursor:default;border:1px solid rgba(0,0,0,0.2);width:20px;height:20px;line-height:20px;text-align:center;vertical-align:middle;padding:2px}.mce-charmap td div{text-align:center}.mce-charmap td:hover{background:#D9D9D9}.mce-grid td.mce-grid-cell div{border:1px solid #d6d6d6;width:15px;height:15px;margin:0;cursor:pointer}.mce-grid td.mce-grid-cell div:focus{border-color:#3498db}.mce-grid td.mce-grid-cell div[disabled]{cursor:not-allowed}.mce-grid{border-spacing:2px;border-collapse:separate}.mce-grid a{display:block;border:1px solid transparent}.mce-grid a:hover,.mce-grid a:focus{border-color:#3498db}.mce-grid-border{margin:0 4px 0 4px}.mce-grid-border a{border-color:#d6d6d6;width:13px;height:13px}.mce-grid-border a:hover,.mce-grid-border a.mce-active{border-color:#3498db;background:#3498db}.mce-text-center{text-align:center}div.mce-tinymce-inline{width:100%}.mce-colorbtn-trans div{text-align:center;vertical-align:middle;font-weight:bold;font-size:20px;line-height:16px;color:#707070}.mce-monospace{font-family:"Courier New",Courier,monospace}.mce-toolbar-grp{padding:2px 0}.mce-toolbar-grp .mce-flow-layout-item{margin-bottom:0}.mce-rtl .mce-wordcount{left:0;right:auto}.mce-croprect-container{position:absolute;top:0;left:0}.mce-croprect-handle{position:absolute;top:0;left:0;width:20px;height:20px;border:2px solid white}.mce-croprect-handle-nw{border-width:2px 0 0 2px;margin:-2px 0 0 -2px;cursor:nw-resize;top:100px;left:100px}.mce-croprect-handle-ne{border-width:2px 2px 0 0;margin:-2px 0 0 -20px;cursor:ne-resize;top:100px;left:200px}.mce-croprect-handle-sw{border-width:0 0 2px 2px;margin:-20px 2px 0 -2px;cursor:sw-resize;top:200px;left:100px}.mce-croprect-handle-se{border-width:0 2px 2px 0;margin:-20px 0 0 -20px;cursor:se-resize;top:200px;left:200px}.mce-croprect-handle-move{position:absolute;cursor:move;border:0}.mce-croprect-block{opacity:.3;filter:alpha(opacity=30);zoom:1;position:absolute;background:black}.mce-croprect-handle:focus{border-color:#3498db}.mce-croprect-handle-move:focus{outline:1px solid #3498db}.mce-imagepanel{overflow:auto;background:black}.mce-imagepanel-bg{position:absolute;background:url('data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==')}.mce-imagepanel img{position:absolute}.mce-imagetool.mce-btn .mce-ico{display:block;width:20px;height:20px;text-align:center;line-height:20px;font-size:20px;padding:5px}.mce-arrow-up{margin-top:12px}.mce-arrow-down{margin-top:-12px}.mce-arrow:before,.mce-arrow:after{position:absolute;left:50%;display:block;width:0;height:0;border-style:solid;border-color:transparent;content:""}.mce-arrow.mce-arrow-up:before{top:-9px;border-bottom-color:rgba(0,0,0,0.2);border-width:0 9px 9px;margin-left:-9px}.mce-arrow.mce-arrow-down:before{bottom:-9px;border-top-color:rgba(0,0,0,0.2);border-width:9px 9px 0;margin-left:-9px}.mce-arrow.mce-arrow-up:after{top:-8px;border-bottom-color:#f0f0f0;border-width:0 8px 8px;margin-left:-8px}.mce-arrow.mce-arrow-down:after{bottom:-8px;border-top-color:#f0f0f0;border-width:8px 8px 0;margin-left:-8px}.mce-arrow.mce-arrow-left:before,.mce-arrow.mce-arrow-left:after{margin:0}.mce-arrow.mce-arrow-left:before{left:8px}.mce-arrow.mce-arrow-left:after{left:9px}.mce-arrow.mce-arrow-right:before,.mce-arrow.mce-arrow-right:after{left:auto;margin:0}.mce-arrow.mce-arrow-right:before{right:8px}.mce-arrow.mce-arrow-right:after{right:9px}.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-left:before{left:-9px;top:50%;border-right-color:rgba(0,0,0,0.2);border-width:9px 9px 9px 0;margin-top:-9px}.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-left:after{left:-8px;top:50%;border-right-color:#f0f0f0;border-width:8px 8px 8px 0;margin-top:-8px}.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-left{margin-left:12px}.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-right:before{right:-9px;top:50%;border-left-color:rgba(0,0,0,0.2);border-width:9px 0 9px 9px;margin-top:-9px}.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-right:after{right:-8px;top:50%;border-left-color:#f0f0f0;border-width:8px 0 8px 8px;margin-top:-8px}.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-right{margin-left:-14px}.mce-container,.mce-container-body{display:block}.mce-autoscroll{overflow:hidden}.mce-scrollbar{position:absolute;width:7px;height:100%;top:2px;right:2px;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-scrollbar-h{top:auto;right:auto;left:2px;bottom:2px;width:100%;height:7px}.mce-scrollbar-thumb{position:absolute;background-color:#000;border:1px solid #888;border-color:rgba(85,85,85,0.6);width:5px;height:100%}.mce-scrollbar-h .mce-scrollbar-thumb{width:100%;height:5px}.mce-scrollbar:hover,.mce-scrollbar.mce-active{background-color:#AAA;opacity:.6;filter:alpha(opacity=60);zoom:1}.mce-scroll{position:relative}.mce-panel{border:0 solid #cacaca;border:0 solid rgba(0,0,0,0.2);background-color:#f0f0f0}.mce-floatpanel{position:absolute}.mce-floatpanel.mce-fixed{position:fixed}.mce-floatpanel .mce-arrow,.mce-floatpanel .mce-arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.mce-floatpanel .mce-arrow{border-width:11px}.mce-floatpanel .mce-arrow:after{border-width:10px;content:""}.mce-floatpanel.mce-popover{filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;top:0;left:0;background:#FFF;border:1px solid rgba(0,0,0,0.2);border:1px solid rgba(0,0,0,0.25)}.mce-floatpanel.mce-popover.mce-bottom{margin-top:10px;*margin-top:0}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:rgba(0,0,0,0.2);border-bottom-color:rgba(0,0,0,0.25);top:-11px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#FFF}.mce-floatpanel.mce-popover.mce-bottom.mce-start{margin-left:-22px}.mce-floatpanel.mce-popover.mce-bottom.mce-start>.mce-arrow{left:20px}.mce-floatpanel.mce-popover.mce-bottom.mce-end{margin-left:22px}.mce-floatpanel.mce-popover.mce-bottom.mce-end>.mce-arrow{right:10px;left:auto}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;height:100%}div.mce-fullscreen{position:fixed;top:0;left:0}#mce-modal-block{opacity:0;filter:alpha(opacity=0);zoom:1;position:fixed;left:0;top:0;width:100%;height:100%;background:#000}#mce-modal-block.mce-in{opacity:.3;filter:alpha(opacity=30);zoom:1}.mce-window-move{cursor:move}.mce-window{filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;background:#FFF;position:fixed;top:0;left:0;opacity:0;transform:scale(.1);transition:transform 100ms ease-in,opacity 150ms ease-in}.mce-window.mce-in{transform:scale(1);opacity:1}.mce-window-head{padding:9px 15px;border-bottom:1px solid #c5c5c5;position:relative}.mce-window-head .mce-close{position:absolute;right:0;top:0;height:38px;width:38px;text-align:center;cursor:pointer}.mce-window-head .mce-close i{color:#858585}.mce-close:hover i{color:#adadad}.mce-window-head .mce-title{line-height:20px;font-size:20px;font-weight:bold;text-rendering:optimizelegibility;padding-right:20px}.mce-window .mce-container-body{display:block}.mce-foot{display:block;background-color:#FFF;border-top:1px solid #c5c5c5}.mce-window-head .mce-dragh{position:absolute;top:0;left:0;cursor:move;width:90%;height:100%}.mce-window iframe{width:100%;height:100%}.mce-window-body .mce-listbox{border-color:#ccc}.mce-rtl .mce-window-head .mce-close{position:absolute;right:auto;left:15px}.mce-rtl .mce-window-head .mce-dragh{left:auto;right:0}.mce-rtl .mce-window-head .mce-title{direction:rtl;text-align:right}.mce-tooltip{position:absolute;padding:5px;opacity:.8;filter:alpha(opacity=80);zoom:1}.mce-tooltip-inner{font-size:11px;background-color:#000;color:white;max-width:200px;padding:5px 8px 4px 8px;text-align:center;white-space:normal}.mce-tooltip-arrow{position:absolute;width:0;height:0;line-height:0;border:5px dashed #000}.mce-tooltip-arrow-n{border-bottom-color:#000}.mce-tooltip-arrow-s{border-top-color:#000}.mce-tooltip-arrow-e{border-left-color:#000}.mce-tooltip-arrow-w{border-right-color:#000}.mce-tooltip-nw,.mce-tooltip-sw{margin-left:-14px}.mce-tooltip-ne,.mce-tooltip-se{margin-left:14px}.mce-tooltip-n .mce-tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-style:solid;border-top:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-nw .mce-tooltip-arrow{top:0;left:10px;border-bottom-style:solid;border-top:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-ne .mce-tooltip-arrow{top:0;right:10px;border-bottom-style:solid;border-top:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-s .mce-tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-style:solid;border-bottom:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-sw .mce-tooltip-arrow{bottom:0;left:10px;border-top-style:solid;border-bottom:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-se .mce-tooltip-arrow{bottom:0;right:10px;border-top-style:solid;border-bottom:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-e .mce-tooltip-arrow{right:0;top:50%;margin-top:-5px;border-left-style:solid;border-right:none;border-top-color:transparent;border-bottom-color:transparent}.mce-tooltip-w .mce-tooltip-arrow{left:0;top:50%;margin-top:-5px;border-right-style:solid;border-left:none;border-top-color:transparent;border-bottom-color:transparent}.mce-progress{display:inline-block;position:relative;height:20px}.mce-progress .mce-bar-container{display:inline-block;width:100px;height:100%;margin-right:8px;border:1px solid #ccc;overflow:hidden}.mce-progress .mce-text{display:inline-block;margin-top:auto;margin-bottom:auto;font-size:14px;width:40px;color:#333}.mce-bar{display:block;width:0;height:100%;background-color:#d7d7d7;-webkit-transition:width .2s ease;transition:width .2s ease}.mce-notification{position:absolute;background-color:#F0F0F0;padding:5px;margin-top:5px;border-width:1px;border-style:solid;border-color:#CCCCCC;transition:transform 100ms ease-in,opacity 150ms ease-in;opacity:0}.mce-notification.mce-in{opacity:1}.mce-notification-success{background-color:#dff0d8;border-color:#d6e9c6}.mce-notification-info{background-color:#d9edf7;border-color:#779ECB}.mce-notification-warning{background-color:#fcf8e3;border-color:#faebcc}.mce-notification-error{background-color:#f2dede;border-color:#ebccd1}.mce-notification.mce-has-close{padding-right:15px}.mce-notification .mce-ico{margin-top:5px}.mce-notification-inner{display:inline-block;font-size:14px;margin:5px 8px 4px 8px;text-align:center;white-space:normal;color:#31708f}.mce-notification-inner a{text-decoration:underline;cursor:pointer}.mce-notification .mce-progress{margin-right:8px}.mce-notification .mce-progress .mce-text{margin-top:5px}.mce-notification *,.mce-notification .mce-progress .mce-text{color:#333333}.mce-notification .mce-progress .mce-bar-container{border-color:#CCCCCC}.mce-notification .mce-progress .mce-bar-container .mce-bar{background-color:#333333}.mce-notification-success *,.mce-notification-success .mce-progress .mce-text{color:#3c763d}.mce-notification-success .mce-progress .mce-bar-container{border-color:#d6e9c6}.mce-notification-success .mce-progress .mce-bar-container .mce-bar{background-color:#3c763d}.mce-notification-info *,.mce-notification-info .mce-progress .mce-text{color:#31708f}.mce-notification-info .mce-progress .mce-bar-container{border-color:#779ECB}.mce-notification-info .mce-progress .mce-bar-container .mce-bar{background-color:#31708f}.mce-notification-warning *,.mce-notification-warning .mce-progress .mce-text{color:#8a6d3b}.mce-notification-warning .mce-progress .mce-bar-container{border-color:#faebcc}.mce-notification-warning .mce-progress .mce-bar-container .mce-bar{background-color:#8a6d3b}.mce-notification-error *,.mce-notification-error .mce-progress .mce-text{color:#a94442}.mce-notification-error .mce-progress .mce-bar-container{border-color:#ebccd1}.mce-notification-error .mce-progress .mce-bar-container .mce-bar{background-color:#a94442}.mce-notification .mce-close{position:absolute;top:6px;right:8px;font-size:20px;font-weight:bold;line-height:20px;color:#858585;cursor:pointer;height:20px;overflow:hidden}.mce-abs-layout{position:relative}body .mce-abs-layout-item,.mce-abs-end{position:absolute}.mce-abs-end{width:1px;height:1px}.mce-container-body.mce-abs-layout{overflow:hidden}.mce-btn{border:1px solid #b1b1b1;border-color:transparent transparent transparent transparent;position:relative;text-shadow:0 1px 1px rgba(255,255,255,0.75);display:inline-block;*display:inline;*zoom:1;background-color:#f0f0f0}.mce-btn:hover,.mce-btn:focus{color:#333;background-color:#e3e3e3;border-color:#ccc}.mce-btn.mce-disabled button,.mce-btn.mce-disabled:hover button{cursor:default;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-btn.mce-active,.mce-btn.mce-active:hover{background-color:#dbdbdb;border-color:#ccc}.mce-btn:active{background-color:#e0e0e0;border-color:#ccc}.mce-btn button{padding:4px 8px;font-size:14px;line-height:20px;*line-height:16px;cursor:pointer;color:#333;text-align:center;overflow:visible;-webkit-appearance:none}.mce-btn button::-moz-focus-inner{border:0;padding:0}.mce-btn i{text-shadow:1px 1px none}.mce-primary.mce-btn-has-text{min-width:50px}.mce-primary{color:#fff;border:1px solid transparent;border-color:transparent;background-color:#2d8ac7}.mce-primary:hover,.mce-primary:focus{background-color:#257cb6;border-color:transparent}.mce-primary.mce-disabled button,.mce-primary.mce-disabled:hover button{cursor:default;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-primary.mce-active,.mce-primary.mce-active:hover,.mce-primary:not(.mce-disabled):active{background-color:#206ea1}.mce-primary button,.mce-primary button i{color:#fff;text-shadow:1px 1px none}.mce-btn .mce-txt{font-size:inherit;line-height:inherit;color:inherit}.mce-btn-large button{padding:9px 14px;font-size:16px;line-height:normal}.mce-btn-large i{margin-top:2px}.mce-btn-small button{padding:1px 5px;font-size:12px;*padding-bottom:2px}.mce-btn-small i{line-height:20px;vertical-align:top;*line-height:18px}.mce-btn .mce-caret{margin-top:8px;margin-left:0}.mce-btn-small .mce-caret{margin-top:8px;margin-left:0}.mce-caret{display:inline-block;*display:inline;*zoom:1;width:0;height:0;vertical-align:top;border-top:4px solid #333;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.mce-disabled .mce-caret{border-top-color:#aaa}.mce-caret.mce-up{border-bottom:4px solid #333;border-top:0}.mce-btn-flat{border:0;background:transparent;filter:none}.mce-btn-flat:hover,.mce-btn-flat.mce-active,.mce-btn-flat:focus,.mce-btn-flat:active{border:0;background:#e6e6e6;filter:none}.mce-btn-has-text .mce-ico{padding-right:5px}.mce-rtl .mce-btn button{direction:rtl}.mce-btn-group .mce-btn{border-width:1px;margin:0;margin-left:2px}.mce-btn-group:not(:first-child){border-left:1px solid #d9d9d9;padding-left:3px;margin-left:3px}.mce-btn-group .mce-first{margin-left:0}.mce-btn-group .mce-btn.mce-flow-layout-item{margin:0}.mce-rtl .mce-btn-group .mce-btn{margin-left:0;margin-right:2px}.mce-rtl .mce-btn-group .mce-first{margin-right:0}.mce-rtl .mce-btn-group:not(:first-child){border-left:none;border-right:1px solid #d9d9d9;padding-right:4px;margin-right:4px}.mce-checkbox{cursor:pointer}i.mce-i-checkbox{margin:0 3px 0 0;border:1px solid #c5c5c5;background-color:#f0f0f0;text-indent:-10em;*font-size:0;*line-height:0;*text-indent:0;overflow:hidden}.mce-checked i.mce-i-checkbox{color:#333;font-size:16px;line-height:16px;text-indent:0}.mce-checkbox:focus i.mce-i-checkbox,.mce-checkbox.mce-focus i.mce-i-checkbox{border:1px solid rgba(82,168,236,0.8)}.mce-checkbox.mce-disabled .mce-label,.mce-checkbox.mce-disabled i.mce-i-checkbox{color:#acacac}.mce-checkbox .mce-label{vertical-align:middle}.mce-rtl .mce-checkbox{direction:rtl;text-align:right}.mce-rtl i.mce-i-checkbox{margin:0 0 0 3px}.mce-combobox{position:relative;display:inline-block;*display:inline;*zoom:1;*height:32px}.mce-combobox input{border:1px solid #c5c5c5;border-right-color:#c5c5c5;height:28px}.mce-combobox.mce-disabled input{color:#adadad}.mce-combobox .mce-btn{border:1px solid #c5c5c5;border-left:0;margin:0}.mce-combobox button{padding-right:8px;padding-left:8px}.mce-combobox.mce-disabled .mce-btn button{cursor:default;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-combobox .mce-status{position:absolute;right:2px;top:50%;line-height:16px;margin-top:-8px;font-size:12px;width:15px;height:15px;text-align:center;cursor:pointer}.mce-combobox.mce-has-status input{padding-right:20px}.mce-combobox.mce-has-open .mce-status{right:37px}.mce-combobox .mce-status.mce-i-warning{color:#c09853}.mce-combobox .mce-status.mce-i-checkmark{color:#468847}.mce-menu.mce-combobox-menu{border-top:0;margin-top:0;max-height:200px}.mce-menu.mce-combobox-menu .mce-menu-item{padding:4px 6px 4px 4px;font-size:11px}.mce-menu.mce-combobox-menu .mce-menu-item-sep{padding:0}.mce-menu.mce-combobox-menu .mce-text{font-size:11px}.mce-menu.mce-combobox-menu .mce-menu-item-link,.mce-menu.mce-combobox-menu .mce-menu-item-link b{font-size:11px}.mce-menu.mce-combobox-menu .mce-text b{font-size:11px}.mce-colorbox i{border:1px solid #c5c5c5;width:14px;height:14px}.mce-colorbutton .mce-ico{position:relative}.mce-colorbutton-grid{margin:4px}.mce-colorbutton button{padding-right:6px;padding-left:6px}.mce-colorbutton .mce-preview{padding-right:3px;display:block;position:absolute;left:50%;top:50%;margin-left:-17px;margin-top:7px;background:gray;width:13px;height:2px;overflow:hidden}.mce-colorbutton.mce-btn-small .mce-preview{margin-left:-16px;padding-right:0;width:16px}.mce-colorbutton .mce-open{padding-left:4px;padding-right:4px;border-left:1px solid transparent}.mce-colorbutton:hover .mce-open{border-color:#ccc}.mce-colorbutton.mce-btn-small .mce-open{padding:0 3px 0 3px}.mce-rtl .mce-colorbutton{direction:rtl}.mce-rtl .mce-colorbutton .mce-preview{margin-left:0;padding-right:0;padding-left:3px}.mce-rtl .mce-colorbutton.mce-btn-small .mce-preview{margin-left:0;padding-right:0;padding-left:2px}.mce-rtl .mce-colorbutton .mce-open{padding-left:4px;padding-right:4px;border-left:0}.mce-colorpicker{position:relative;width:250px;height:220px}.mce-colorpicker-sv{position:absolute;top:0;left:0;width:90%;height:100%;border:1px solid #c5c5c5;cursor:crosshair;overflow:hidden}.mce-colorpicker-h-chunk{width:100%}.mce-colorpicker-overlay1,.mce-colorpicker-overlay2{width:100%;height:100%;position:absolute;top:0;left:0}.mce-colorpicker-overlay1{filter:progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr='#ffffff', endColorstr='#00ffffff');-ms-filter:"progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr='#ffffff', endColorstr='#00ffffff')";background:linear-gradient(to right, #fff, rgba(255,255,255,0))}.mce-colorpicker-overlay2{filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#00000000', endColorstr='#000000');-ms-filter:"progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#00000000', endColorstr='#000000')";background:linear-gradient(to bottom, rgba(0,0,0,0), #000)}.mce-colorpicker-selector1{background:none;position:absolute;width:12px;height:12px;margin:-8px 0 0 -8px;border:1px solid black;border-radius:50%}.mce-colorpicker-selector2{position:absolute;width:10px;height:10px;border:1px solid white;border-radius:50%}.mce-colorpicker-h{position:absolute;top:0;right:0;width:6.5%;height:100%;border:1px solid #c5c5c5;cursor:crosshair}.mce-colorpicker-h-marker{margin-top:-4px;position:absolute;top:0;left:-1px;width:100%;border:1px solid #333;background:#fff;height:4px;z-index:100}.mce-path{display:inline-block;*display:inline;*zoom:1;padding:8px;white-space:normal}.mce-path .mce-txt{display:inline-block;padding-right:3px}.mce-path .mce-path-body{display:inline-block}.mce-path-item{display:inline-block;*display:inline;*zoom:1;cursor:pointer;color:#333}.mce-path-item:hover{text-decoration:underline}.mce-path-item:focus{background:#666;color:#fff}.mce-path .mce-divider{display:inline}.mce-disabled .mce-path-item{color:#aaa}.mce-rtl .mce-path{direction:rtl}.mce-fieldset{border:0 solid #9E9E9E}.mce-fieldset>.mce-container-body{margin-top:-15px}.mce-fieldset-title{margin-left:5px;padding:0 5px 0 5px}.mce-fit-layout{display:inline-block;*display:inline;*zoom:1}.mce-fit-layout-item{position:absolute}.mce-flow-layout-item{display:inline-block;*display:inline;*zoom:1}.mce-flow-layout-item{margin:2px 0 2px 2px}.mce-flow-layout-item.mce-last{margin-right:2px}.mce-flow-layout{white-space:normal}.mce-tinymce-inline .mce-flow-layout{white-space:nowrap}.mce-rtl .mce-flow-layout{text-align:right;direction:rtl}.mce-rtl .mce-flow-layout-item{margin:2px 2px 2px 0}.mce-rtl .mce-flow-layout-item.mce-last{margin-left:2px}.mce-iframe{border:0 solid rgba(0,0,0,0.2);width:100%;height:100%}.mce-infobox{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 1px rgba(255,255,255,0.75);overflow:hidden;border:1px solid red}.mce-infobox div{display:block;margin:5px}.mce-infobox div button{position:absolute;top:50%;right:4px;cursor:pointer;margin-top:-8px;display:none}.mce-infobox div button:focus{outline:2px solid #ccc}.mce-infobox.mce-has-help div{margin-right:25px}.mce-infobox.mce-has-help button{display:block}.mce-infobox.mce-success{background:#dff0d8;border-color:#d6e9c6}.mce-infobox.mce-success div{color:#3c763d}.mce-infobox.mce-warning{background:#fcf8e3;border-color:#faebcc}.mce-infobox.mce-warning div{color:#8a6d3b}.mce-infobox.mce-error{background:#f2dede;border-color:#ebccd1}.mce-infobox.mce-error div{color:#a94442}.mce-rtl .mce-infobox div{text-align:right;direction:rtl}.mce-label{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 1px rgba(255,255,255,0.75);overflow:hidden}.mce-label.mce-autoscroll{overflow:auto}.mce-label.mce-disabled{color:#aaa}.mce-label.mce-multiline{white-space:pre-wrap}.mce-label.mce-success{color:#468847}.mce-label.mce-warning{color:#c09853}.mce-label.mce-error{color:#b94a48}.mce-rtl .mce-label{text-align:right;direction:rtl}.mce-menubar .mce-menubtn{border-color:transparent;background:transparent;filter:none}.mce-menubar .mce-menubtn button{color:#333}.mce-menubar{border:1px solid rgba(217,217,217,0.52)}.mce-menubar .mce-menubtn button span{color:#333}.mce-menubar .mce-caret{border-top-color:#333}.mce-menubar .mce-menubtn:hover,.mce-menubar .mce-menubtn.mce-active,.mce-menubar .mce-menubtn:focus{border-color:#ccc;background:#fff;filter:none}.mce-menubtn button{color:#333}.mce-menubtn.mce-btn-small span{font-size:12px}.mce-menubtn.mce-fixed-width span{display:inline-block;overflow-x:hidden;text-overflow:ellipsis;width:90px}.mce-menubtn.mce-fixed-width.mce-btn-small span{width:70px}.mce-menubtn .mce-caret{*margin-top:6px}.mce-rtl .mce-menubtn button{direction:rtl;text-align:right}.mce-menu-item{display:block;padding:6px 15px 6px 12px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap;cursor:pointer;line-height:normal;border-left:4px solid transparent;margin-bottom:1px}.mce-menu-item .mce-ico,.mce-menu-item .mce-text{color:#333}.mce-menu-item.mce-disabled .mce-text,.mce-menu-item.mce-disabled .mce-ico{color:#adadad}.mce-menu-item:hover .mce-text,.mce-menu-item.mce-selected .mce-text,.mce-menu-item:focus .mce-text{color:white}.mce-menu-item:hover .mce-ico,.mce-menu-item.mce-selected .mce-ico,.mce-menu-item:focus .mce-ico{color:white}.mce-menu-item.mce-disabled:hover{background:#CCC}.mce-menu-shortcut{display:inline-block;color:#adadad}.mce-menu-shortcut{display:inline-block;*display:inline;*zoom:1;padding:0 15px 0 20px}.mce-menu-item:hover .mce-menu-shortcut,.mce-menu-item.mce-selected .mce-menu-shortcut,.mce-menu-item:focus .mce-menu-shortcut{color:white}.mce-menu-item .mce-caret{margin-top:4px;*margin-top:3px;margin-right:6px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid #333}.mce-menu-item.mce-selected .mce-caret,.mce-menu-item:focus .mce-caret,.mce-menu-item:hover .mce-caret{border-left-color:white}.mce-menu-align .mce-menu-shortcut{*margin-top:-2px}.mce-menu-align .mce-menu-shortcut,.mce-menu-align .mce-caret{position:absolute;right:0}.mce-menu-item.mce-active i{visibility:visible}.mce-menu-item-normal.mce-active{background-color:#3498db}.mce-menu-item-preview.mce-active{border-left:5px solid #aaa}.mce-menu-item-normal.mce-active .mce-text{color:white}.mce-menu-item-normal.mce-active:hover .mce-text,.mce-menu-item-normal.mce-active:hover .mce-ico{color:white}.mce-menu-item-normal.mce-active:focus .mce-text,.mce-menu-item-normal.mce-active:focus .mce-ico{color:white}.mce-menu-item:hover,.mce-menu-item.mce-selected,.mce-menu-item:focus{text-decoration:none;color:white;background-color:#2d8ac7}.mce-menu-item-link{color:#093;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mce-menu-item-link b{color:#093}.mce-menu-item-ellipsis{display:block;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.mce-menu-item:hover *,.mce-menu-item.mce-selected *,.mce-menu-item:focus *{color:white}div.mce-menu .mce-menu-item-sep,.mce-menu-item-sep:hover{border:0;padding:0;height:1px;margin:9px 1px;overflow:hidden;background:transparent;border-bottom:1px solid rgba(0,0,0,0.1);cursor:default;filter:none}div.mce-menu .mce-menu-item b{font-weight:bold}.mce-menu-item-indent-1{padding-left:20px}.mce-menu-item-indent-2{padding-left:35px}.mce-menu-item-indent-2{padding-left:35px}.mce-menu-item-indent-3{padding-left:40px}.mce-menu-item-indent-4{padding-left:45px}.mce-menu-item-indent-5{padding-left:50px}.mce-menu-item-indent-6{padding-left:55px}.mce-menu.mce-rtl{direction:rtl}.mce-rtl .mce-menu-item{text-align:right;direction:rtl;padding:6px 12px 6px 15px}.mce-menu-align.mce-rtl .mce-menu-shortcut,.mce-menu-align.mce-rtl .mce-caret{right:auto;left:0}.mce-rtl .mce-menu-item .mce-caret{margin-left:6px;margin-right:0;border-right:4px solid #333;border-left:0}.mce-rtl .mce-menu-item.mce-selected .mce-caret,.mce-rtl .mce-menu-item:focus .mce-caret,.mce-rtl .mce-menu-item:hover .mce-caret{border-left-color:transparent;border-right-color:white}.mce-throbber{position:absolute;top:0;left:0;width:100%;height:100%;opacity:.6;filter:alpha(opacity=60);zoom:1;background:#fff url('img/loader.gif') no-repeat center center}.mce-throbber-inline{position:static;height:50px}.mce-menu .mce-throbber-inline{height:25px;background-size:contain}.mce-menu{position:absolute;left:0;top:0;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;z-index:1000;padding:5px 0 5px 0;margin:-1px 0 0;min-width:160px;background:#fff;border:1px solid #989898;border:1px solid rgba(0,0,0,0.2);z-index:1002;max-height:400px;overflow:auto;overflow-x:hidden}.mce-menu i{display:none}.mce-menu-has-icons i{display:inline-block;*display:inline}.mce-menu-sub-tr-tl{margin:-6px 0 0 -1px}.mce-menu-sub-br-bl{margin:6px 0 0 -1px}.mce-menu-sub-tl-tr{margin:-6px 0 0 1px}.mce-menu-sub-bl-br{margin:6px 0 0 1px}.mce-listbox button{text-align:left;padding-right:20px;position:relative}.mce-listbox .mce-caret{position:absolute;margin-top:-2px;right:8px;top:50%}.mce-rtl .mce-listbox .mce-caret{right:auto;left:8px}.mce-rtl .mce-listbox button{padding-right:10px;padding-left:20px}.mce-container-body .mce-resizehandle{position:absolute;right:0;bottom:0;width:16px;height:16px;visibility:visible;cursor:s-resize;margin:0}.mce-container-body .mce-resizehandle-both{cursor:se-resize}i.mce-i-resize{color:#333}.mce-selectbox{background:#fff;border:1px solid #c5c5c5}.mce-slider{border:1px solid #AAA;background:#EEE;width:100px;height:10px;position:relative;display:block}.mce-slider.mce-vertical{width:10px;height:100px}.mce-slider-handle{border:1px solid #BBB;background:#DDD;display:block;width:13px;height:13px;position:absolute;top:0;left:0;margin-left:-1px;margin-top:-2px}.mce-slider-handle:focus{background:#BBB}.mce-spacer{visibility:hidden}.mce-splitbtn .mce-open{border-left:1px solid transparent}.mce-splitbtn:hover .mce-open{border-left-color:#ccc}.mce-splitbtn button{padding-right:6px;padding-left:6px}.mce-splitbtn .mce-open{padding-right:4px;padding-left:4px}.mce-splitbtn .mce-open.mce-active{background-color:#dbdbdb;outline:1px solid #ccc}.mce-splitbtn.mce-btn-small .mce-open{padding:0 3px 0 3px}.mce-rtl .mce-splitbtn{direction:rtl;text-align:right}.mce-rtl .mce-splitbtn button{padding-right:4px;padding-left:4px}.mce-rtl .mce-splitbtn .mce-open{border-left:0}.mce-stack-layout-item{display:block}.mce-tabs{display:block;border-bottom:1px solid #c5c5c5}.mce-tabs,.mce-tabs+.mce-container-body{background:#FFF}.mce-tab{display:inline-block;*display:inline;*zoom:1;border:1px solid #c5c5c5;border-width:0 1px 0 0;background:#ffffff;padding:8px;text-shadow:0 1px 1px rgba(255,255,255,0.75);height:13px;cursor:pointer}.mce-tab:hover{background:#FDFDFD}.mce-tab.mce-active{background:#FDFDFD;border-bottom-color:transparent;margin-bottom:-1px;height:14px}.mce-rtl .mce-tabs{text-align:right;direction:rtl}.mce-rtl .mce-tab{border-width:0 0 0 1px}.mce-textbox{background:#fff;border:1px solid #c5c5c5;display:inline-block;-webkit-transition:border linear .2s, box-shadow linear .2s;transition:border linear .2s, box-shadow linear .2s;height:28px;resize:none;padding:0 4px 0 4px;white-space:pre-wrap;*white-space:pre;color:#333}.mce-textbox:focus,.mce-textbox.mce-focus{border-color:#3498db}.mce-placeholder .mce-textbox{color:#aaa}.mce-textbox.mce-multiline{padding:4px;height:auto}.mce-textbox.mce-disabled{color:#adadad}.mce-rtl .mce-textbox{text-align:right;direction:rtl}@font-face{font-family:'tinymce';src:url('fonts/tinymce.eot');src:url('fonts/tinymce.eot?#iefix') format('embedded-opentype'),url('fonts/tinymce.woff') format('woff'),url('fonts/tinymce.ttf') format('truetype'),url('fonts/tinymce.svg#tinymce') format('svg');font-weight:normal;font-style:normal}@font-face{font-family:'tinymce-small';src:url('fonts/tinymce-small.eot');src:url('fonts/tinymce-small.eot?#iefix') format('embedded-opentype'),url('fonts/tinymce-small.woff') format('woff'),url('fonts/tinymce-small.ttf') format('truetype'),url('fonts/tinymce-small.svg#tinymce') format('svg');font-weight:normal;font-style:normal}.mce-ico{font-family:'tinymce';font-style:normal;font-weight:normal;font-size:16px;line-height:16px;vertical-align:text-top;-webkit-font-smoothing:antialiased;display:inline-block;background:transparent center center;width:16px;height:16px;color:#333;-ie7-icon:' '}.mce-btn-small .mce-ico{font-family:'tinymce-small'}.mce-ico,i.mce-i-checkbox{zoom:expression(this.runtimeStyle['zoom'] = '1', this.innerHTML = this.currentStyle['-ie7-icon'].substr(1, 1) + ' ')}.mce-i-save{-ie7-icon:"\e000"}.mce-i-newdocument{-ie7-icon:"\e001"}.mce-i-fullpage{-ie7-icon:"\e002"}.mce-i-alignleft{-ie7-icon:"\e003"}.mce-i-aligncenter{-ie7-icon:"\e004"}.mce-i-alignright{-ie7-icon:"\e005"}.mce-i-alignjustify{-ie7-icon:"\e006"}.mce-i-alignnone{-ie7-icon:"\e003"}.mce-i-cut{-ie7-icon:"\e007"}.mce-i-paste{-ie7-icon:"\e008"}.mce-i-searchreplace{-ie7-icon:"\e009"}.mce-i-bullist{-ie7-icon:"\e00a"}.mce-i-numlist{-ie7-icon:"\e00b"}.mce-i-indent{-ie7-icon:"\e00c"}.mce-i-outdent{-ie7-icon:"\e00d"}.mce-i-blockquote{-ie7-icon:"\e00e"}.mce-i-undo{-ie7-icon:"\e00f"}.mce-i-redo{-ie7-icon:"\e010"}.mce-i-link{-ie7-icon:"\e011"}.mce-i-unlink{-ie7-icon:"\e012"}.mce-i-anchor{-ie7-icon:"\e013"}.mce-i-image{-ie7-icon:"\e014"}.mce-i-media{-ie7-icon:"\e015"}.mce-i-help{-ie7-icon:"\e016"}.mce-i-code{-ie7-icon:"\e017"}.mce-i-insertdatetime{-ie7-icon:"\e018"}.mce-i-preview{-ie7-icon:"\e019"}.mce-i-forecolor{-ie7-icon:"\e01a"}.mce-i-backcolor{-ie7-icon:"\e01a"}.mce-i-table{-ie7-icon:"\e01b"}.mce-i-hr{-ie7-icon:"\e01c"}.mce-i-removeformat{-ie7-icon:"\e01d"}.mce-i-subscript{-ie7-icon:"\e01e"}.mce-i-superscript{-ie7-icon:"\e01f"}.mce-i-charmap{-ie7-icon:"\e020"}.mce-i-emoticons{-ie7-icon:"\e021"}.mce-i-print{-ie7-icon:"\e022"}.mce-i-fullscreen{-ie7-icon:"\e023"}.mce-i-spellchecker{-ie7-icon:"\e024"}.mce-i-nonbreaking{-ie7-icon:"\e025"}.mce-i-template{-ie7-icon:"\e026"}.mce-i-pagebreak{-ie7-icon:"\e027"}.mce-i-restoredraft{-ie7-icon:"\e028"}.mce-i-untitled{-ie7-icon:"\e029"}.mce-i-bold{-ie7-icon:"\e02a"}.mce-i-italic{-ie7-icon:"\e02b"}.mce-i-underline{-ie7-icon:"\e02c"}.mce-i-strikethrough{-ie7-icon:"\e02d"}.mce-i-visualchars{-ie7-icon:"\e02e"}.mce-i-ltr{-ie7-icon:"\e02f"}.mce-i-rtl{-ie7-icon:"\e030"}.mce-i-copy{-ie7-icon:"\e031"}.mce-i-resize{-ie7-icon:"\e032"}.mce-i-browse{-ie7-icon:"\e034"}.mce-i-pastetext{-ie7-icon:"\e035"}.mce-i-rotateleft{-ie7-icon:"\eaa8"}.mce-i-rotateright{-ie7-icon:"\eaa9"}.mce-i-crop{-ie7-icon:"\ee78"}.mce-i-editimage{-ie7-icon:"\e914"}.mce-i-options{-ie7-icon:"\ec6a"}.mce-i-flipv{-ie7-icon:"\eaaa"}.mce-i-fliph{-ie7-icon:"\eaac"}.mce-i-zoomin{-ie7-icon:"\eb35"}.mce-i-zoomout{-ie7-icon:"\eb36"}.mce-i-sun{-ie7-icon:"\eccc"}.mce-i-moon{-ie7-icon:"\eccd"}.mce-i-arrowleft{-ie7-icon:"\edc0"}.mce-i-arrowright{-ie7-icon:"\edb8"}.mce-i-drop{-ie7-icon:"\e934"}.mce-i-contrast{-ie7-icon:"\ecd4"}.mce-i-sharpen{-ie7-icon:"\eba7"}.mce-i-palette{-ie7-icon:"\e92a"}.mce-i-resize2{-ie7-icon:"\edf9"}.mce-i-orientation{-ie7-icon:"\e601"}.mce-i-invert{-ie7-icon:"\e602"}.mce-i-gamma{-ie7-icon:"\e600"}.mce-i-remove{-ie7-icon:"\ed6a"}.mce-i-codesample{-ie7-icon:"\e603"}.mce-i-checkbox,.mce-i-selected{-ie7-icon:"\e033"}.mce-i-selected{visibility:hidden}.mce-i-backcolor{background:#BBB} -------------------------------------------------------------------------------- /site/src/assets/skins/lightgray/skin.min.css: -------------------------------------------------------------------------------- 1 | .mce-container,.mce-container *,.mce-widget,.mce-widget *,.mce-reset{margin:0;padding:0;border:0;outline:0;vertical-align:top;background:transparent;text-decoration:none;color:#333;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;text-shadow:none;float:none;position:static;width:auto;height:auto;white-space:nowrap;cursor:inherit;-webkit-tap-highlight-color:transparent;line-height:normal;font-weight:normal;text-align:left;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;direction:ltr;max-width:none}.mce-widget button{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.mce-container *[unselectable]{-moz-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}.mce-fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.mce-fade.mce-in{opacity:1}.mce-tinymce{visibility:inherit !important;position:relative}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;height:100%;z-index:100}div.mce-fullscreen{position:fixed;top:0;left:0;width:100%;height:auto}.mce-tinymce{display:block}.mce-wordcount{position:absolute;top:0;right:0;padding:8px}div.mce-edit-area{background:#FFF;filter:none}.mce-statusbar{position:relative}.mce-statusbar .mce-container-body{position:relative}.mce-fullscreen .mce-resizehandle{display:none}.mce-charmap{border-collapse:collapse}.mce-charmap td{cursor:default;border:1px solid rgba(0,0,0,0.2);width:20px;height:20px;line-height:20px;text-align:center;vertical-align:middle;padding:2px}.mce-charmap td div{text-align:center}.mce-charmap td:hover{background:#D9D9D9}.mce-grid td.mce-grid-cell div{border:1px solid #d6d6d6;width:15px;height:15px;margin:0;cursor:pointer}.mce-grid td.mce-grid-cell div:focus{border-color:#3498db}.mce-grid td.mce-grid-cell div[disabled]{cursor:not-allowed}.mce-grid{border-spacing:2px;border-collapse:separate}.mce-grid a{display:block;border:1px solid transparent}.mce-grid a:hover,.mce-grid a:focus{border-color:#3498db}.mce-grid-border{margin:0 4px 0 4px}.mce-grid-border a{border-color:#d6d6d6;width:13px;height:13px}.mce-grid-border a:hover,.mce-grid-border a.mce-active{border-color:#3498db;background:#3498db}.mce-text-center{text-align:center}div.mce-tinymce-inline{width:100%}.mce-colorbtn-trans div{text-align:center;vertical-align:middle;font-weight:bold;font-size:20px;line-height:16px;color:#707070}.mce-monospace{font-family:"Courier New",Courier,monospace}.mce-toolbar-grp{padding:2px 0}.mce-toolbar-grp .mce-flow-layout-item{margin-bottom:0}.mce-rtl .mce-wordcount{left:0;right:auto}.mce-croprect-container{position:absolute;top:0;left:0}.mce-croprect-handle{position:absolute;top:0;left:0;width:20px;height:20px;border:2px solid white}.mce-croprect-handle-nw{border-width:2px 0 0 2px;margin:-2px 0 0 -2px;cursor:nw-resize;top:100px;left:100px}.mce-croprect-handle-ne{border-width:2px 2px 0 0;margin:-2px 0 0 -20px;cursor:ne-resize;top:100px;left:200px}.mce-croprect-handle-sw{border-width:0 0 2px 2px;margin:-20px 2px 0 -2px;cursor:sw-resize;top:200px;left:100px}.mce-croprect-handle-se{border-width:0 2px 2px 0;margin:-20px 0 0 -20px;cursor:se-resize;top:200px;left:200px}.mce-croprect-handle-move{position:absolute;cursor:move;border:0}.mce-croprect-block{opacity:.3;filter:alpha(opacity=30);zoom:1;position:absolute;background:black}.mce-croprect-handle:focus{border-color:#3498db}.mce-croprect-handle-move:focus{outline:1px solid #3498db}.mce-imagepanel{overflow:auto;background:black}.mce-imagepanel-bg{position:absolute;background:url('data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==')}.mce-imagepanel img{position:absolute}.mce-imagetool.mce-btn .mce-ico{display:block;width:20px;height:20px;text-align:center;line-height:20px;font-size:20px;padding:5px}.mce-arrow-up{margin-top:12px}.mce-arrow-down{margin-top:-12px}.mce-arrow:before,.mce-arrow:after{position:absolute;left:50%;display:block;width:0;height:0;border-style:solid;border-color:transparent;content:""}.mce-arrow.mce-arrow-up:before{top:-9px;border-bottom-color:rgba(0,0,0,0.2);border-width:0 9px 9px;margin-left:-9px}.mce-arrow.mce-arrow-down:before{bottom:-9px;border-top-color:rgba(0,0,0,0.2);border-width:9px 9px 0;margin-left:-9px}.mce-arrow.mce-arrow-up:after{top:-8px;border-bottom-color:#f0f0f0;border-width:0 8px 8px;margin-left:-8px}.mce-arrow.mce-arrow-down:after{bottom:-8px;border-top-color:#f0f0f0;border-width:8px 8px 0;margin-left:-8px}.mce-arrow.mce-arrow-left:before,.mce-arrow.mce-arrow-left:after{margin:0}.mce-arrow.mce-arrow-left:before{left:8px}.mce-arrow.mce-arrow-left:after{left:9px}.mce-arrow.mce-arrow-right:before,.mce-arrow.mce-arrow-right:after{left:auto;margin:0}.mce-arrow.mce-arrow-right:before{right:8px}.mce-arrow.mce-arrow-right:after{right:9px}.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-left:before{left:-9px;top:50%;border-right-color:rgba(0,0,0,0.2);border-width:9px 9px 9px 0;margin-top:-9px}.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-left:after{left:-8px;top:50%;border-right-color:#f0f0f0;border-width:8px 8px 8px 0;margin-top:-8px}.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-left{margin-left:12px}.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-right:before{right:-9px;top:50%;border-left-color:rgba(0,0,0,0.2);border-width:9px 0 9px 9px;margin-top:-9px}.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-right:after{right:-8px;top:50%;border-left-color:#f0f0f0;border-width:8px 0 8px 8px;margin-top:-8px}.mce-arrow.mce-arrow-center.mce-arrow.mce-arrow-right{margin-left:-14px}.mce-edit-aria-container>.mce-container-body{display:flex}.mce-edit-aria-container>.mce-container-body .mce-edit-area{flex:1}.mce-edit-aria-container>.mce-container-body .mce-sidebar>.mce-container-body{display:flex;align-items:stretch;height:100%}.mce-edit-aria-container>.mce-container-body .mce-sidebar-panel{min-width:250px;max-width:250px;position:relative}.mce-edit-aria-container>.mce-container-body .mce-sidebar-panel>.mce-container-body{position:absolute;width:100%;height:100%;overflow:auto;top:0;left:0}.mce-sidebar-toolbar{border:0 solid rgba(0,0,0,0.2);border-left-width:1px}.mce-sidebar-toolbar .mce-btn.mce-active,.mce-sidebar-toolbar .mce-btn.mce-active:hover{border:1px solid transparent;border-color:transparent;background-color:#2d8ac7}.mce-sidebar-toolbar .mce-btn.mce-active button,.mce-sidebar-toolbar .mce-btn.mce-active:hover button,.mce-sidebar-toolbar .mce-btn.mce-active button i,.mce-sidebar-toolbar .mce-btn.mce-active:hover button i{color:#fff;text-shadow:1px 1px none}.mce-sidebar-panel{border:0 solid rgba(0,0,0,0.2);border-left-width:1px}.mce-container,.mce-container-body{display:block}.mce-autoscroll{overflow:hidden}.mce-scrollbar{position:absolute;width:7px;height:100%;top:2px;right:2px;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-scrollbar-h{top:auto;right:auto;left:2px;bottom:2px;width:100%;height:7px}.mce-scrollbar-thumb{position:absolute;background-color:#000;border:1px solid #888;border-color:rgba(85,85,85,0.6);width:5px;height:100%}.mce-scrollbar-h .mce-scrollbar-thumb{width:100%;height:5px}.mce-scrollbar:hover,.mce-scrollbar.mce-active{background-color:#AAA;opacity:.6;filter:alpha(opacity=60);zoom:1}.mce-scroll{position:relative}.mce-panel{border:0 solid #cacaca;border:0 solid rgba(0,0,0,0.2);background-color:#f0f0f0}.mce-floatpanel{position:absolute}.mce-floatpanel.mce-fixed{position:fixed}.mce-floatpanel .mce-arrow,.mce-floatpanel .mce-arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.mce-floatpanel .mce-arrow{border-width:11px}.mce-floatpanel .mce-arrow:after{border-width:10px;content:""}.mce-floatpanel.mce-popover{filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;top:0;left:0;background:#FFF;border:1px solid rgba(0,0,0,0.2);border:1px solid rgba(0,0,0,0.25)}.mce-floatpanel.mce-popover.mce-bottom{margin-top:10px;*margin-top:0}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:rgba(0,0,0,0.2);border-bottom-color:rgba(0,0,0,0.25);top:-11px}.mce-floatpanel.mce-popover.mce-bottom>.mce-arrow:after{top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#FFF}.mce-floatpanel.mce-popover.mce-bottom.mce-start{margin-left:-22px}.mce-floatpanel.mce-popover.mce-bottom.mce-start>.mce-arrow{left:20px}.mce-floatpanel.mce-popover.mce-bottom.mce-end{margin-left:22px}.mce-floatpanel.mce-popover.mce-bottom.mce-end>.mce-arrow{right:10px;left:auto}.mce-fullscreen{border:0;padding:0;margin:0;overflow:hidden;height:100%}div.mce-fullscreen{position:fixed;top:0;left:0}#mce-modal-block{opacity:0;filter:alpha(opacity=0);zoom:1;position:fixed;left:0;top:0;width:100%;height:100%;background:#000}#mce-modal-block.mce-in{opacity:.3;filter:alpha(opacity=30);zoom:1}.mce-window-move{cursor:move}.mce-window{filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;background:#FFF;position:fixed;top:0;left:0;opacity:0;transform:scale(.1);transition:transform 100ms ease-in,opacity 150ms ease-in}.mce-window.mce-in{transform:scale(1);opacity:1}.mce-window-head{padding:9px 15px;border-bottom:1px solid #c5c5c5;position:relative}.mce-window-head .mce-close{position:absolute;right:0;top:0;height:38px;width:38px;text-align:center;cursor:pointer}.mce-window-head .mce-close i{color:#858585}.mce-close:hover i{color:#adadad}.mce-window-head .mce-title{line-height:20px;font-size:20px;font-weight:bold;text-rendering:optimizelegibility;padding-right:20px}.mce-window .mce-container-body{display:block}.mce-foot{display:block;background-color:#FFF;border-top:1px solid #c5c5c5}.mce-window-head .mce-dragh{position:absolute;top:0;left:0;cursor:move;width:90%;height:100%}.mce-window iframe{width:100%;height:100%}.mce-window-body .mce-listbox{border-color:#ccc}.mce-rtl .mce-window-head .mce-close{position:absolute;right:auto;left:15px}.mce-rtl .mce-window-head .mce-dragh{left:auto;right:0}.mce-rtl .mce-window-head .mce-title{direction:rtl;text-align:right}.mce-tooltip{position:absolute;padding:5px;opacity:.8;filter:alpha(opacity=80);zoom:1}.mce-tooltip-inner{font-size:11px;background-color:#000;color:white;max-width:200px;padding:5px 8px 4px 8px;text-align:center;white-space:normal}.mce-tooltip-arrow{position:absolute;width:0;height:0;line-height:0;border:5px dashed #000}.mce-tooltip-arrow-n{border-bottom-color:#000}.mce-tooltip-arrow-s{border-top-color:#000}.mce-tooltip-arrow-e{border-left-color:#000}.mce-tooltip-arrow-w{border-right-color:#000}.mce-tooltip-nw,.mce-tooltip-sw{margin-left:-14px}.mce-tooltip-ne,.mce-tooltip-se{margin-left:14px}.mce-tooltip-n .mce-tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-style:solid;border-top:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-nw .mce-tooltip-arrow{top:0;left:10px;border-bottom-style:solid;border-top:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-ne .mce-tooltip-arrow{top:0;right:10px;border-bottom-style:solid;border-top:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-s .mce-tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-style:solid;border-bottom:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-sw .mce-tooltip-arrow{bottom:0;left:10px;border-top-style:solid;border-bottom:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-se .mce-tooltip-arrow{bottom:0;right:10px;border-top-style:solid;border-bottom:none;border-left-color:transparent;border-right-color:transparent}.mce-tooltip-e .mce-tooltip-arrow{right:0;top:50%;margin-top:-5px;border-left-style:solid;border-right:none;border-top-color:transparent;border-bottom-color:transparent}.mce-tooltip-w .mce-tooltip-arrow{left:0;top:50%;margin-top:-5px;border-right-style:solid;border-left:none;border-top-color:transparent;border-bottom-color:transparent}.mce-progress{display:inline-block;position:relative;height:20px}.mce-progress .mce-bar-container{display:inline-block;width:100px;height:100%;margin-right:8px;border:1px solid #ccc;overflow:hidden}.mce-progress .mce-text{display:inline-block;margin-top:auto;margin-bottom:auto;font-size:14px;width:40px;color:#333}.mce-bar{display:block;width:0;height:100%;background-color:#d7d7d7;-webkit-transition:width .2s ease;transition:width .2s ease}.mce-notification{position:absolute;background-color:#F0F0F0;padding:5px;margin-top:5px;border-width:1px;border-style:solid;border-color:#CCCCCC;transition:transform 100ms ease-in,opacity 150ms ease-in;opacity:0}.mce-notification.mce-in{opacity:1}.mce-notification-success{background-color:#dff0d8;border-color:#d6e9c6}.mce-notification-info{background-color:#d9edf7;border-color:#779ECB}.mce-notification-warning{background-color:#fcf8e3;border-color:#faebcc}.mce-notification-error{background-color:#f2dede;border-color:#ebccd1}.mce-notification.mce-has-close{padding-right:15px}.mce-notification .mce-ico{margin-top:5px}.mce-notification-inner{display:inline-block;font-size:14px;margin:5px 8px 4px 8px;text-align:center;white-space:normal;color:#31708f}.mce-notification-inner a{text-decoration:underline;cursor:pointer}.mce-notification .mce-progress{margin-right:8px}.mce-notification .mce-progress .mce-text{margin-top:5px}.mce-notification *,.mce-notification .mce-progress .mce-text{color:#333333}.mce-notification .mce-progress .mce-bar-container{border-color:#CCCCCC}.mce-notification .mce-progress .mce-bar-container .mce-bar{background-color:#333333}.mce-notification-success *,.mce-notification-success .mce-progress .mce-text{color:#3c763d}.mce-notification-success .mce-progress .mce-bar-container{border-color:#d6e9c6}.mce-notification-success .mce-progress .mce-bar-container .mce-bar{background-color:#3c763d}.mce-notification-info *,.mce-notification-info .mce-progress .mce-text{color:#31708f}.mce-notification-info .mce-progress .mce-bar-container{border-color:#779ECB}.mce-notification-info .mce-progress .mce-bar-container .mce-bar{background-color:#31708f}.mce-notification-warning *,.mce-notification-warning .mce-progress .mce-text{color:#8a6d3b}.mce-notification-warning .mce-progress .mce-bar-container{border-color:#faebcc}.mce-notification-warning .mce-progress .mce-bar-container .mce-bar{background-color:#8a6d3b}.mce-notification-error *,.mce-notification-error .mce-progress .mce-text{color:#a94442}.mce-notification-error .mce-progress .mce-bar-container{border-color:#ebccd1}.mce-notification-error .mce-progress .mce-bar-container .mce-bar{background-color:#a94442}.mce-notification .mce-close{position:absolute;top:6px;right:8px;font-size:20px;font-weight:bold;line-height:20px;color:#858585;cursor:pointer;height:20px;overflow:hidden}.mce-abs-layout{position:relative}body .mce-abs-layout-item,.mce-abs-end{position:absolute}.mce-abs-end{width:1px;height:1px}.mce-container-body.mce-abs-layout{overflow:hidden}.mce-btn{border:1px solid #b1b1b1;border-color:transparent transparent transparent transparent;position:relative;text-shadow:0 1px 1px rgba(255,255,255,0.75);display:inline-block;*display:inline;*zoom:1;background-color:#f0f0f0}.mce-btn:hover,.mce-btn:focus{color:#333;background-color:#e3e3e3;border-color:#ccc}.mce-btn.mce-disabled button,.mce-btn.mce-disabled:hover button{cursor:default;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-btn.mce-active,.mce-btn.mce-active:hover{background-color:#dbdbdb;border-color:#ccc}.mce-btn:active{background-color:#e0e0e0;border-color:#ccc}.mce-btn button{padding:4px 8px;font-size:14px;line-height:20px;*line-height:16px;cursor:pointer;color:#333;text-align:center;overflow:visible;-webkit-appearance:none}.mce-btn button::-moz-focus-inner{border:0;padding:0}.mce-btn i{text-shadow:1px 1px none}.mce-primary.mce-btn-has-text{min-width:50px}.mce-primary{color:#fff;border:1px solid transparent;border-color:transparent;background-color:#2d8ac7}.mce-primary:hover,.mce-primary:focus{background-color:#257cb6;border-color:transparent}.mce-primary.mce-disabled button,.mce-primary.mce-disabled:hover button{cursor:default;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-primary.mce-active,.mce-primary.mce-active:hover,.mce-primary:not(.mce-disabled):active{background-color:#206ea1}.mce-primary button,.mce-primary button i{color:#fff;text-shadow:1px 1px none}.mce-btn .mce-txt{font-size:inherit;line-height:inherit;color:inherit}.mce-btn-large button{padding:9px 14px;font-size:16px;line-height:normal}.mce-btn-large i{margin-top:2px}.mce-btn-small button{padding:1px 5px;font-size:12px;*padding-bottom:2px}.mce-btn-small i{line-height:20px;vertical-align:top;*line-height:18px}.mce-btn .mce-caret{margin-top:8px;margin-left:0}.mce-btn-small .mce-caret{margin-top:8px;margin-left:0}.mce-caret{display:inline-block;*display:inline;*zoom:1;width:0;height:0;vertical-align:top;border-top:4px solid #333;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.mce-disabled .mce-caret{border-top-color:#aaa}.mce-caret.mce-up{border-bottom:4px solid #333;border-top:0}.mce-btn-flat{border:0;background:transparent;filter:none}.mce-btn-flat:hover,.mce-btn-flat.mce-active,.mce-btn-flat:focus,.mce-btn-flat:active{border:0;background:#e6e6e6;filter:none}.mce-btn-has-text .mce-ico{padding-right:5px}.mce-rtl .mce-btn button{direction:rtl}.mce-btn-group .mce-btn{border-width:1px;margin:0;margin-left:2px}.mce-btn-group:not(:first-child){border-left:1px solid #d9d9d9;padding-left:3px;margin-left:3px}.mce-btn-group .mce-first{margin-left:0}.mce-btn-group .mce-btn.mce-flow-layout-item{margin:0}.mce-rtl .mce-btn-group .mce-btn{margin-left:0;margin-right:2px}.mce-rtl .mce-btn-group .mce-first{margin-right:0}.mce-rtl .mce-btn-group:not(:first-child){border-left:none;border-right:1px solid #d9d9d9;padding-right:4px;margin-right:4px}.mce-checkbox{cursor:pointer}i.mce-i-checkbox{margin:0 3px 0 0;border:1px solid #c5c5c5;background-color:#f0f0f0;text-indent:-10em;*font-size:0;*line-height:0;*text-indent:0;overflow:hidden}.mce-checked i.mce-i-checkbox{color:#333;font-size:16px;line-height:16px;text-indent:0}.mce-checkbox:focus i.mce-i-checkbox,.mce-checkbox.mce-focus i.mce-i-checkbox{border:1px solid rgba(82,168,236,0.8)}.mce-checkbox.mce-disabled .mce-label,.mce-checkbox.mce-disabled i.mce-i-checkbox{color:#acacac}.mce-checkbox .mce-label{vertical-align:middle}.mce-rtl .mce-checkbox{direction:rtl;text-align:right}.mce-rtl i.mce-i-checkbox{margin:0 0 0 3px}.mce-combobox{position:relative;display:inline-block;*display:inline;*zoom:1;*height:32px}.mce-combobox input{border:1px solid #c5c5c5;border-right-color:#c5c5c5;height:28px}.mce-combobox.mce-disabled input{color:#adadad}.mce-combobox .mce-btn{border:1px solid #c5c5c5;border-left:0;margin:0}.mce-combobox button{padding-right:8px;padding-left:8px}.mce-combobox.mce-disabled .mce-btn button{cursor:default;opacity:.4;filter:alpha(opacity=40);zoom:1}.mce-combobox .mce-status{position:absolute;right:2px;top:50%;line-height:16px;margin-top:-8px;font-size:12px;width:15px;height:15px;text-align:center;cursor:pointer}.mce-combobox.mce-has-status input{padding-right:20px}.mce-combobox.mce-has-open .mce-status{right:37px}.mce-combobox .mce-status.mce-i-warning{color:#c09853}.mce-combobox .mce-status.mce-i-checkmark{color:#468847}.mce-menu.mce-combobox-menu{border-top:0;margin-top:0;max-height:200px}.mce-menu.mce-combobox-menu .mce-menu-item{padding:4px 6px 4px 4px;font-size:11px}.mce-menu.mce-combobox-menu .mce-menu-item-sep{padding:0}.mce-menu.mce-combobox-menu .mce-text{font-size:11px}.mce-menu.mce-combobox-menu .mce-menu-item-link,.mce-menu.mce-combobox-menu .mce-menu-item-link b{font-size:11px}.mce-menu.mce-combobox-menu .mce-text b{font-size:11px}.mce-colorbox i{border:1px solid #c5c5c5;width:14px;height:14px}.mce-colorbutton .mce-ico{position:relative}.mce-colorbutton-grid{margin:4px}.mce-colorbutton button{padding-right:6px;padding-left:6px}.mce-colorbutton .mce-preview{padding-right:3px;display:block;position:absolute;left:50%;top:50%;margin-left:-17px;margin-top:7px;background:gray;width:13px;height:2px;overflow:hidden}.mce-colorbutton.mce-btn-small .mce-preview{margin-left:-16px;padding-right:0;width:16px}.mce-colorbutton .mce-open{padding-left:4px;padding-right:4px;border-left:1px solid transparent}.mce-colorbutton:hover .mce-open{border-color:#ccc}.mce-colorbutton.mce-btn-small .mce-open{padding:0 3px 0 3px}.mce-rtl .mce-colorbutton{direction:rtl}.mce-rtl .mce-colorbutton .mce-preview{margin-left:0;padding-right:0;padding-left:3px}.mce-rtl .mce-colorbutton.mce-btn-small .mce-preview{margin-left:0;padding-right:0;padding-left:2px}.mce-rtl .mce-colorbutton .mce-open{padding-left:4px;padding-right:4px;border-left:0}.mce-colorpicker{position:relative;width:250px;height:220px}.mce-colorpicker-sv{position:absolute;top:0;left:0;width:90%;height:100%;border:1px solid #c5c5c5;cursor:crosshair;overflow:hidden}.mce-colorpicker-h-chunk{width:100%}.mce-colorpicker-overlay1,.mce-colorpicker-overlay2{width:100%;height:100%;position:absolute;top:0;left:0}.mce-colorpicker-overlay1{filter:progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr='#ffffff', endColorstr='#00ffffff');-ms-filter:"progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr='#ffffff', endColorstr='#00ffffff')";background:linear-gradient(to right, #fff, rgba(255,255,255,0))}.mce-colorpicker-overlay2{filter:progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#00000000', endColorstr='#000000');-ms-filter:"progid:DXImageTransform.Microsoft.gradient(GradientType=0,startColorstr='#00000000', endColorstr='#000000')";background:linear-gradient(to bottom, rgba(0,0,0,0), #000)}.mce-colorpicker-selector1{background:none;position:absolute;width:12px;height:12px;margin:-8px 0 0 -8px;border:1px solid black;border-radius:50%}.mce-colorpicker-selector2{position:absolute;width:10px;height:10px;border:1px solid white;border-radius:50%}.mce-colorpicker-h{position:absolute;top:0;right:0;width:6.5%;height:100%;border:1px solid #c5c5c5;cursor:crosshair}.mce-colorpicker-h-marker{margin-top:-4px;position:absolute;top:0;left:-1px;width:100%;border:1px solid #333;background:#fff;height:4px;z-index:100}.mce-path{display:inline-block;*display:inline;*zoom:1;padding:8px;white-space:normal}.mce-path .mce-txt{display:inline-block;padding-right:3px}.mce-path .mce-path-body{display:inline-block}.mce-path-item{display:inline-block;*display:inline;*zoom:1;cursor:pointer;color:#333}.mce-path-item:hover{text-decoration:underline}.mce-path-item:focus{background:#666;color:#fff}.mce-path .mce-divider{display:inline}.mce-disabled .mce-path-item{color:#aaa}.mce-rtl .mce-path{direction:rtl}.mce-fieldset{border:0 solid #9E9E9E}.mce-fieldset>.mce-container-body{margin-top:-15px}.mce-fieldset-title{margin-left:5px;padding:0 5px 0 5px}.mce-fit-layout{display:inline-block;*display:inline;*zoom:1}.mce-fit-layout-item{position:absolute}.mce-flow-layout-item{display:inline-block;*display:inline;*zoom:1}.mce-flow-layout-item{margin:2px 0 2px 2px}.mce-flow-layout-item.mce-last{margin-right:2px}.mce-flow-layout{white-space:normal}.mce-tinymce-inline .mce-flow-layout{white-space:nowrap}.mce-rtl .mce-flow-layout{text-align:right;direction:rtl}.mce-rtl .mce-flow-layout-item{margin:2px 2px 2px 0}.mce-rtl .mce-flow-layout-item.mce-last{margin-left:2px}.mce-iframe{border:0 solid rgba(0,0,0,0.2);width:100%;height:100%}.mce-infobox{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 1px rgba(255,255,255,0.75);overflow:hidden;border:1px solid red}.mce-infobox div{display:block;margin:5px}.mce-infobox div button{position:absolute;top:50%;right:4px;cursor:pointer;margin-top:-8px;display:none}.mce-infobox div button:focus{outline:2px solid #ccc}.mce-infobox.mce-has-help div{margin-right:25px}.mce-infobox.mce-has-help button{display:block}.mce-infobox.mce-success{background:#dff0d8;border-color:#d6e9c6}.mce-infobox.mce-success div{color:#3c763d}.mce-infobox.mce-warning{background:#fcf8e3;border-color:#faebcc}.mce-infobox.mce-warning div{color:#8a6d3b}.mce-infobox.mce-error{background:#f2dede;border-color:#ebccd1}.mce-infobox.mce-error div{color:#a94442}.mce-rtl .mce-infobox div{text-align:right;direction:rtl}.mce-label{display:inline-block;*display:inline;*zoom:1;text-shadow:0 1px 1px rgba(255,255,255,0.75);overflow:hidden}.mce-label.mce-autoscroll{overflow:auto}.mce-label.mce-disabled{color:#aaa}.mce-label.mce-multiline{white-space:pre-wrap}.mce-label.mce-success{color:#468847}.mce-label.mce-warning{color:#c09853}.mce-label.mce-error{color:#b94a48}.mce-rtl .mce-label{text-align:right;direction:rtl}.mce-menubar .mce-menubtn{border-color:transparent;background:transparent;filter:none}.mce-menubar .mce-menubtn button{color:#333}.mce-menubar{border:1px solid rgba(217,217,217,0.52)}.mce-menubar .mce-menubtn button span{color:#333}.mce-menubar .mce-caret{border-top-color:#333}.mce-menubar .mce-menubtn:hover,.mce-menubar .mce-menubtn.mce-active,.mce-menubar .mce-menubtn:focus{border-color:#ccc;background:#fff;filter:none}.mce-menubtn button{color:#333}.mce-menubtn.mce-btn-small span{font-size:12px}.mce-menubtn.mce-fixed-width span{display:inline-block;overflow-x:hidden;text-overflow:ellipsis;width:90px}.mce-menubtn.mce-fixed-width.mce-btn-small span{width:70px}.mce-menubtn .mce-caret{*margin-top:6px}.mce-rtl .mce-menubtn button{direction:rtl;text-align:right}.mce-menu-item{display:block;padding:6px 15px 6px 12px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap;cursor:pointer;line-height:normal;border-left:4px solid transparent;margin-bottom:1px}.mce-menu-item .mce-ico,.mce-menu-item .mce-text{color:#333}.mce-menu-item.mce-disabled .mce-text,.mce-menu-item.mce-disabled .mce-ico{color:#adadad}.mce-menu-item:hover .mce-text,.mce-menu-item.mce-selected .mce-text,.mce-menu-item:focus .mce-text{color:white}.mce-menu-item:hover .mce-ico,.mce-menu-item.mce-selected .mce-ico,.mce-menu-item:focus .mce-ico{color:white}.mce-menu-item.mce-disabled:hover{background:#CCC}.mce-menu-shortcut{display:inline-block;color:#adadad}.mce-menu-shortcut{display:inline-block;*display:inline;*zoom:1;padding:0 15px 0 20px}.mce-menu-item:hover .mce-menu-shortcut,.mce-menu-item.mce-selected .mce-menu-shortcut,.mce-menu-item:focus .mce-menu-shortcut{color:white}.mce-menu-item .mce-caret{margin-top:4px;*margin-top:3px;margin-right:6px;border-top:4px solid transparent;border-bottom:4px solid transparent;border-left:4px solid #333}.mce-menu-item.mce-selected .mce-caret,.mce-menu-item:focus .mce-caret,.mce-menu-item:hover .mce-caret{border-left-color:white}.mce-menu-align .mce-menu-shortcut{*margin-top:-2px}.mce-menu-align .mce-menu-shortcut,.mce-menu-align .mce-caret{position:absolute;right:0}.mce-menu-item.mce-active i{visibility:visible}.mce-menu-item-normal.mce-active{background-color:#3498db}.mce-menu-item-preview.mce-active{border-left:5px solid #aaa}.mce-menu-item-normal.mce-active .mce-text{color:white}.mce-menu-item-normal.mce-active:hover .mce-text,.mce-menu-item-normal.mce-active:hover .mce-ico{color:white}.mce-menu-item-normal.mce-active:focus .mce-text,.mce-menu-item-normal.mce-active:focus .mce-ico{color:white}.mce-menu-item:hover,.mce-menu-item.mce-selected,.mce-menu-item:focus{text-decoration:none;color:white;background-color:#2d8ac7}.mce-menu-item-link{color:#093;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mce-menu-item-link b{color:#093}.mce-menu-item-ellipsis{display:block;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.mce-menu-item:hover *,.mce-menu-item.mce-selected *,.mce-menu-item:focus *{color:white}div.mce-menu .mce-menu-item-sep,.mce-menu-item-sep:hover{border:0;padding:0;height:1px;margin:9px 1px;overflow:hidden;background:transparent;border-bottom:1px solid rgba(0,0,0,0.1);cursor:default;filter:none}div.mce-menu .mce-menu-item b{font-weight:bold}.mce-menu-item-indent-1{padding-left:20px}.mce-menu-item-indent-2{padding-left:35px}.mce-menu-item-indent-2{padding-left:35px}.mce-menu-item-indent-3{padding-left:40px}.mce-menu-item-indent-4{padding-left:45px}.mce-menu-item-indent-5{padding-left:50px}.mce-menu-item-indent-6{padding-left:55px}.mce-menu.mce-rtl{direction:rtl}.mce-rtl .mce-menu-item{text-align:right;direction:rtl;padding:6px 12px 6px 15px}.mce-menu-align.mce-rtl .mce-menu-shortcut,.mce-menu-align.mce-rtl .mce-caret{right:auto;left:0}.mce-rtl .mce-menu-item .mce-caret{margin-left:6px;margin-right:0;border-right:4px solid #333;border-left:0}.mce-rtl .mce-menu-item.mce-selected .mce-caret,.mce-rtl .mce-menu-item:focus .mce-caret,.mce-rtl .mce-menu-item:hover .mce-caret{border-left-color:transparent;border-right-color:white}.mce-throbber{position:absolute;top:0;left:0;width:100%;height:100%;opacity:.6;filter:alpha(opacity=60);zoom:1;background:#fff url('img/loader.gif') no-repeat center center}.mce-throbber-inline{position:static;height:50px}.mce-menu .mce-throbber-inline{height:25px;background-size:contain}.mce-menu{position:absolute;left:0;top:0;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background:transparent;z-index:1000;padding:5px 0 5px 0;margin:-1px 0 0;min-width:160px;background:#fff;border:1px solid #989898;border:1px solid rgba(0,0,0,0.2);z-index:1002;max-height:400px;overflow:auto;overflow-x:hidden}.mce-menu i{display:none}.mce-menu-has-icons i{display:inline-block;*display:inline}.mce-menu-sub-tr-tl{margin:-6px 0 0 -1px}.mce-menu-sub-br-bl{margin:6px 0 0 -1px}.mce-menu-sub-tl-tr{margin:-6px 0 0 1px}.mce-menu-sub-bl-br{margin:6px 0 0 1px}.mce-listbox button{text-align:left;padding-right:20px;position:relative}.mce-listbox .mce-caret{position:absolute;margin-top:-2px;right:8px;top:50%}.mce-rtl .mce-listbox .mce-caret{right:auto;left:8px}.mce-rtl .mce-listbox button{padding-right:10px;padding-left:20px}.mce-container-body .mce-resizehandle{position:absolute;right:0;bottom:0;width:16px;height:16px;visibility:visible;cursor:s-resize;margin:0}.mce-container-body .mce-resizehandle-both{cursor:se-resize}i.mce-i-resize{color:#333}.mce-selectbox{background:#fff;border:1px solid #c5c5c5}.mce-slider{border:1px solid #AAA;background:#EEE;width:100px;height:10px;position:relative;display:block}.mce-slider.mce-vertical{width:10px;height:100px}.mce-slider-handle{border:1px solid #BBB;background:#DDD;display:block;width:13px;height:13px;position:absolute;top:0;left:0;margin-left:-1px;margin-top:-2px}.mce-slider-handle:focus{background:#BBB}.mce-spacer{visibility:hidden}.mce-splitbtn .mce-open{border-left:1px solid transparent}.mce-splitbtn:hover .mce-open{border-left-color:#ccc}.mce-splitbtn button{padding-right:6px;padding-left:6px}.mce-splitbtn .mce-open{padding-right:4px;padding-left:4px}.mce-splitbtn .mce-open.mce-active{background-color:#dbdbdb;outline:1px solid #ccc}.mce-splitbtn.mce-btn-small .mce-open{padding:0 3px 0 3px}.mce-rtl .mce-splitbtn{direction:rtl;text-align:right}.mce-rtl .mce-splitbtn button{padding-right:4px;padding-left:4px}.mce-rtl .mce-splitbtn .mce-open{border-left:0}.mce-stack-layout-item{display:block}.mce-tabs{display:block;border-bottom:1px solid #c5c5c5}.mce-tabs,.mce-tabs+.mce-container-body{background:#FFF}.mce-tab{display:inline-block;*display:inline;*zoom:1;border:1px solid #c5c5c5;border-width:0 1px 0 0;background:#ffffff;padding:8px;text-shadow:0 1px 1px rgba(255,255,255,0.75);height:13px;cursor:pointer}.mce-tab:hover{background:#FDFDFD}.mce-tab.mce-active{background:#FDFDFD;border-bottom-color:transparent;margin-bottom:-1px;height:14px}.mce-rtl .mce-tabs{text-align:right;direction:rtl}.mce-rtl .mce-tab{border-width:0 0 0 1px}.mce-textbox{background:#fff;border:1px solid #c5c5c5;display:inline-block;-webkit-transition:border linear .2s, box-shadow linear .2s;transition:border linear .2s, box-shadow linear .2s;height:28px;resize:none;padding:0 4px 0 4px;white-space:pre-wrap;*white-space:pre;color:#333}.mce-textbox:focus,.mce-textbox.mce-focus{border-color:#3498db}.mce-placeholder .mce-textbox{color:#aaa}.mce-textbox.mce-multiline{padding:4px;height:auto}.mce-textbox.mce-disabled{color:#adadad}.mce-rtl .mce-textbox{text-align:right;direction:rtl}@font-face{font-family:'tinymce';src:url('fonts/tinymce.eot');src:url('fonts/tinymce.eot?#iefix') format('embedded-opentype'),url('fonts/tinymce.woff') format('woff'),url('fonts/tinymce.ttf') format('truetype'),url('fonts/tinymce.svg#tinymce') format('svg');font-weight:normal;font-style:normal}@font-face{font-family:'tinymce-small';src:url('fonts/tinymce-small.eot');src:url('fonts/tinymce-small.eot?#iefix') format('embedded-opentype'),url('fonts/tinymce-small.woff') format('woff'),url('fonts/tinymce-small.ttf') format('truetype'),url('fonts/tinymce-small.svg#tinymce') format('svg');font-weight:normal;font-style:normal}.mce-ico{font-family:'tinymce',Arial;font-style:normal;font-weight:normal;font-variant:normal;font-size:16px;line-height:16px;speak:none;vertical-align:text-top;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:inline-block;background:transparent center center;background-size:cover;width:16px;height:16px;color:#333}.mce-btn-small .mce-ico{font-family:'tinymce-small',Arial}.mce-i-save:before{content:"\e000"}.mce-i-newdocument:before{content:"\e001"}.mce-i-fullpage:before{content:"\e002"}.mce-i-alignleft:before{content:"\e003"}.mce-i-aligncenter:before{content:"\e004"}.mce-i-alignright:before{content:"\e005"}.mce-i-alignjustify:before{content:"\e006"}.mce-i-alignnone:before{content:"\e003"}.mce-i-cut:before{content:"\e007"}.mce-i-paste:before{content:"\e008"}.mce-i-searchreplace:before{content:"\e009"}.mce-i-bullist:before{content:"\e00a"}.mce-i-numlist:before{content:"\e00b"}.mce-i-indent:before{content:"\e00c"}.mce-i-outdent:before{content:"\e00d"}.mce-i-blockquote:before{content:"\e00e"}.mce-i-undo:before{content:"\e00f"}.mce-i-redo:before{content:"\e010"}.mce-i-link:before{content:"\e011"}.mce-i-unlink:before{content:"\e012"}.mce-i-anchor:before{content:"\e013"}.mce-i-image:before{content:"\e014"}.mce-i-media:before{content:"\e015"}.mce-i-help:before{content:"\e016"}.mce-i-code:before{content:"\e017"}.mce-i-insertdatetime:before{content:"\e018"}.mce-i-preview:before{content:"\e019"}.mce-i-forecolor:before{content:"\e01a"}.mce-i-backcolor:before{content:"\e01a"}.mce-i-table:before{content:"\e01b"}.mce-i-hr:before{content:"\e01c"}.mce-i-removeformat:before{content:"\e01d"}.mce-i-subscript:before{content:"\e01e"}.mce-i-superscript:before{content:"\e01f"}.mce-i-charmap:before{content:"\e020"}.mce-i-emoticons:before{content:"\e021"}.mce-i-print:before{content:"\e022"}.mce-i-fullscreen:before{content:"\e023"}.mce-i-spellchecker:before{content:"\e024"}.mce-i-nonbreaking:before{content:"\e025"}.mce-i-template:before{content:"\e026"}.mce-i-pagebreak:before{content:"\e027"}.mce-i-restoredraft:before{content:"\e028"}.mce-i-bold:before{content:"\e02a"}.mce-i-italic:before{content:"\e02b"}.mce-i-underline:before{content:"\e02c"}.mce-i-strikethrough:before{content:"\e02d"}.mce-i-visualchars:before{content:"\e02e"}.mce-i-visualblocks:before{content:"\e02e"}.mce-i-ltr:before{content:"\e02f"}.mce-i-rtl:before{content:"\e030"}.mce-i-copy:before{content:"\e031"}.mce-i-resize:before{content:"\e032"}.mce-i-browse:before{content:"\e034"}.mce-i-pastetext:before{content:"\e035"}.mce-i-rotateleft:before{content:"\eaa8"}.mce-i-rotateright:before{content:"\eaa9"}.mce-i-crop:before{content:"\ee78"}.mce-i-editimage:before{content:"\e915"}.mce-i-options:before{content:"\ec6a"}.mce-i-flipv:before{content:"\eaaa"}.mce-i-fliph:before{content:"\eaac"}.mce-i-zoomin:before{content:"\eb35"}.mce-i-zoomout:before{content:"\eb36"}.mce-i-sun:before{content:"\eccc"}.mce-i-moon:before{content:"\eccd"}.mce-i-arrowleft:before{content:"\edc0"}.mce-i-arrowright:before{content:"\e93c"}.mce-i-drop:before{content:"\e935"}.mce-i-contrast:before{content:"\ecd4"}.mce-i-sharpen:before{content:"\eba7"}.mce-i-resize2:before{content:"\edf9"}.mce-i-orientation:before{content:"\e601"}.mce-i-invert:before{content:"\e602"}.mce-i-gamma:before{content:"\e600"}.mce-i-remove:before{content:"\ed6a"}.mce-i-tablerowprops:before{content:"\e604"}.mce-i-tablecellprops:before{content:"\e605"}.mce-i-table2:before{content:"\e606"}.mce-i-tablemergecells:before{content:"\e607"}.mce-i-tableinsertcolbefore:before{content:"\e608"}.mce-i-tableinsertcolafter:before{content:"\e609"}.mce-i-tableinsertrowbefore:before{content:"\e60a"}.mce-i-tableinsertrowafter:before{content:"\e60b"}.mce-i-tablesplitcells:before{content:"\e60d"}.mce-i-tabledelete:before{content:"\e60e"}.mce-i-tableleftheader:before{content:"\e62a"}.mce-i-tabletopheader:before{content:"\e62b"}.mce-i-tabledeleterow:before{content:"\e800"}.mce-i-tabledeletecol:before{content:"\e801"}.mce-i-codesample:before{content:"\e603"}.mce-i-fill:before{content:"\e902"}.mce-i-borderwidth:before{content:"\e903"}.mce-i-line:before{content:"\e904"}.mce-i-count:before{content:"\e905"}.mce-i-translate:before{content:"\e907"}.mce-i-drag:before{content:"\e908"}.mce-i-home:before{content:"\e90b"}.mce-i-upload:before{content:"\e914"}.mce-i-bubble:before{content:"\e91c"}.mce-i-user:before{content:"\e91d"}.mce-i-lock:before{content:"\e926"}.mce-i-unlock:before{content:"\e927"}.mce-i-settings:before{content:"\e928"}.mce-i-remove2:before{content:"\e92a"}.mce-i-menu:before{content:"\e92d"}.mce-i-warning:before{content:"\e930"}.mce-i-question:before{content:"\e931"}.mce-i-pluscircle:before{content:"\e932"}.mce-i-info:before{content:"\e933"}.mce-i-notice:before{content:"\e934"}.mce-i-arrowup:before{content:"\e93b"}.mce-i-arrowdown:before{content:"\e93d"}.mce-i-arrowup2:before{content:"\e93f"}.mce-i-arrowdown2:before{content:"\e940"}.mce-i-menu2:before{content:"\e941"}.mce-i-newtab:before{content:"\e961"}.mce-i-a11y:before{content:"\e900"}.mce-i-plus:before{content:"\e93a"}.mce-i-insert:before{content:"\e93a"}.mce-i-minus:before{content:"\e939"}.mce-i-books:before{content:"\e911"}.mce-i-reload:before{content:"\e906"}.mce-i-toc:before{content:"\e901"}.mce-i-checkmark:before{content:"\e033"}.mce-i-checkbox:before,.mce-i-selected:before{content:"\e033"}.mce-i-insert{font-size:14px}.mce-i-selected{visibility:hidden}i.mce-i-backcolor{text-shadow:none;background:#BBB} -------------------------------------------------------------------------------- /site/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /site/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 | -------------------------------------------------------------------------------- /site/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tabvn/angular-blog/845a53b861cd992c704cf52ca01b32ecf3f08d68/site/src/favicon.ico -------------------------------------------------------------------------------- /site/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Blog 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Loading... 24 | 25 | 26 | -------------------------------------------------------------------------------- /site/src/main.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { enableProdMode } from '@angular/core'; 5 | import { environment } from './environments/environment'; 6 | import { AppModule } from './app/'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule); 13 | -------------------------------------------------------------------------------- /site/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | // This file includes polyfills needed by Angular 2 and is loaded before 2 | // the app. You can add your own extra polyfills to this file. 3 | import 'core-js/es6/symbol'; 4 | import 'core-js/es6/object'; 5 | import 'core-js/es6/function'; 6 | import 'core-js/es6/parse-int'; 7 | import 'core-js/es6/parse-float'; 8 | import 'core-js/es6/number'; 9 | import 'core-js/es6/math'; 10 | import 'core-js/es6/string'; 11 | import 'core-js/es6/date'; 12 | import 'core-js/es6/array'; 13 | import 'core-js/es6/regexp'; 14 | import 'core-js/es6/map'; 15 | import 'core-js/es6/set'; 16 | import 'core-js/es6/reflect'; 17 | 18 | import 'core-js/es7/reflect'; 19 | import 'zone.js/dist/zone'; 20 | -------------------------------------------------------------------------------- /site/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | .blog-list article { 3 | border-bottom: 1px solid #eee; 4 | padding-bottom: 30px; 5 | margin-bottom: 30px; 6 | 7 | } 8 | 9 | .user-blog-post { 10 | border-bottom: 1px solid #eee; 11 | padding-bottom: 30px; 12 | } 13 | 14 | .tab-actions { 15 | margin-bottom: 30px; 16 | } 17 | 18 | .page-title { 19 | margin-bottom: 30px; 20 | } 21 | 22 | .load-more { 23 | background-color: #eeeeee; 24 | display: inline-block; 25 | text-align: center; 26 | padding: 8px 15px; 27 | cursor: pointer; 28 | } 29 | 30 | .is-loading { 31 | -animation: spin .7s infinite linear; 32 | -ms-animation: spin .7s infinite linear; 33 | -webkit-animation: spinw .7s infinite linear; 34 | -moz-animation: spinm .7s infinite linear; 35 | } 36 | 37 | @keyframes spin { 38 | from { 39 | transform: scale(1) rotate(0deg); 40 | } 41 | to { 42 | transform: scale(1) rotate(360deg); 43 | } 44 | } 45 | 46 | @-webkit-keyframes spinw { 47 | from { 48 | -webkit-transform: rotate(0deg); 49 | } 50 | to { 51 | -webkit-transform: rotate(360deg); 52 | } 53 | } 54 | 55 | @-moz-keyframes spinm { 56 | from { 57 | -moz-transform: rotate(0deg); 58 | } 59 | to { 60 | -moz-transform: rotate(360deg); 61 | } 62 | } 63 | 64 | .search-block { 65 | padding: 10px; 66 | } 67 | 68 | .search-block .dropdown-menu { 69 | 70 | width: 100%; 71 | } 72 | .search-block .dropdown-menu li{ 73 | padding: 3px 5px; 74 | cursor: pointer; 75 | 76 | } 77 | 78 | #sidebar{ 79 | padding: 0 0 0 20px; 80 | } 81 | -------------------------------------------------------------------------------- /site/src/test.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | 10 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 11 | declare var __karma__: any; 12 | declare var require: any; 13 | 14 | // Prevent Karma from running prematurely. 15 | __karma__.loaded = function () {}; 16 | 17 | 18 | Promise.all([ 19 | System.import('@angular/core/testing'), 20 | System.import('@angular/platform-browser-dynamic/testing') 21 | ]) 22 | // First, initialize the Angular testing environment. 23 | .then(([testing, testingBrowser]) => { 24 | testing.getTestBed().initTestEnvironment( 25 | testingBrowser.BrowserDynamicTestingModule, 26 | testingBrowser.platformBrowserDynamicTesting() 27 | ); 28 | }) 29 | // Then we find all the tests. 30 | .then(() => require.context('./', true, /\.spec\.ts/)) 31 | // And load the modules. 32 | .then(context => context.keys().map(context)) 33 | // Finally, start Karma to run the tests. 34 | .then(__karma__.start, __karma__.error); 35 | -------------------------------------------------------------------------------- /site/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": false, 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "lib": ["es6", "dom"], 7 | "mapRoot": "./", 8 | "module": "es6", 9 | "moduleResolution": "node", 10 | "outDir": "../dist/out-tsc", 11 | "sourceMap": true, 12 | "target": "es5", 13 | "typeRoots": [ 14 | "../node_modules/@types" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /site/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | // Typings reference file, you can add your own global typings here 2 | // https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html 3 | 4 | declare var System: any; 5 | declare var tinymce: any; 6 | 7 | -------------------------------------------------------------------------------- /site/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "class-name": true, 7 | "comment-format": [ 8 | true, 9 | "check-space" 10 | ], 11 | "curly": true, 12 | "eofline": true, 13 | "forin": true, 14 | "indent": [ 15 | true, 16 | "spaces" 17 | ], 18 | "label-position": true, 19 | "label-undefined": true, 20 | "max-line-length": [ 21 | true, 22 | 140 23 | ], 24 | "member-access": false, 25 | "member-ordering": [ 26 | true, 27 | "static-before-instance", 28 | "variables-before-functions" 29 | ], 30 | "no-arg": true, 31 | "no-bitwise": true, 32 | "no-console": [ 33 | true, 34 | "debug", 35 | "info", 36 | "time", 37 | "timeEnd", 38 | "trace" 39 | ], 40 | "no-construct": true, 41 | "no-debugger": true, 42 | "no-duplicate-key": true, 43 | "no-duplicate-variable": true, 44 | "no-empty": false, 45 | "no-eval": true, 46 | "no-inferrable-types": true, 47 | "no-shadowed-variable": true, 48 | "no-string-literal": false, 49 | "no-switch-case-fall-through": true, 50 | "no-trailing-whitespace": true, 51 | "no-unused-expression": true, 52 | "no-unused-variable": true, 53 | "no-unreachable": true, 54 | "no-use-before-declare": true, 55 | "no-var-keyword": true, 56 | "object-literal-sort-keys": false, 57 | "one-line": [ 58 | true, 59 | "check-open-brace", 60 | "check-catch", 61 | "check-else", 62 | "check-whitespace" 63 | ], 64 | "quotemark": [ 65 | true, 66 | "single" 67 | ], 68 | "radix": true, 69 | "semicolon": [ 70 | "always" 71 | ], 72 | "triple-equals": [ 73 | true, 74 | "allow-null-check" 75 | ], 76 | "typedef-whitespace": [ 77 | true, 78 | { 79 | "call-signature": "nospace", 80 | "index-signature": "nospace", 81 | "parameter": "nospace", 82 | "property-declaration": "nospace", 83 | "variable-declaration": "nospace" 84 | } 85 | ], 86 | "variable-name": false, 87 | "whitespace": [ 88 | true, 89 | "check-branch", 90 | "check-decl", 91 | "check-operator", 92 | "check-separator", 93 | "check-type" 94 | ], 95 | 96 | "directive-selector-prefix": [true, "app"], 97 | "component-selector-prefix": [true, "app"], 98 | "directive-selector-name": [true, "camelCase"], 99 | "component-selector-name": [true, "kebab-case"], 100 | "directive-selector-type": [true, "attribute"], 101 | "component-selector-type": [true, "element"], 102 | "use-input-property-decorator": true, 103 | "use-output-property-decorator": true, 104 | "use-host-property-decorator": true, 105 | "no-input-rename": true, 106 | "no-output-rename": true, 107 | "use-life-cycle-interface": true, 108 | "use-pipe-transform-interface": true, 109 | "component-class-suffix": true, 110 | "directive-class-suffix": true, 111 | "templates-use-public": true, 112 | "invoke-injectable": true 113 | } 114 | } 115 | --------------------------------------------------------------------------------