├── .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 |
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 |
9 | {{post.body}}
10 |
11 |
12 |
13 |
14 |
22 |
23 |
24 |
25 |
26 |
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 |
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 |
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 |
6 |
7 |
8 |
9 | {{post.title}}
10 |
11 |
12 |
13 |
14 |
23 |
24 |
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 |
10 |
11 |
12 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
Comments:
16 |17 |-
18 |
{{comment.body}}
20 |
21 |
22 |{{comment.name}} says: {{comment.title}}
19 |