├── .editorconfig
├── .gitignore
├── .nojekyll
├── .prettierrc
├── .vscode
├── extensions.json
├── team-essentials
│ ├── config.json
│ ├── filters.json
│ └── settings.json
├── team.json
└── user.json
├── 404.html
├── CNAME
├── README.md
├── aurelia_project
├── aurelia.json
├── environments
│ ├── dev.ts
│ ├── prod.ts
│ └── stage.ts
├── generators
│ ├── attribute.json
│ ├── attribute.ts
│ ├── binding-behavior.json
│ ├── binding-behavior.ts
│ ├── component.json
│ ├── component.ts
│ ├── element.json
│ ├── element.ts
│ ├── generator.json
│ ├── generator.ts
│ ├── task.json
│ ├── task.ts
│ ├── value-converter.json
│ └── value-converter.ts
└── tasks
│ ├── build.json
│ ├── build.ts
│ ├── environment.ts
│ ├── jest.json
│ ├── jest.ts
│ ├── run.json
│ └── run.ts
├── custom_typings
├── fetch.d.ts
├── system.d.ts
└── window.d.ts
├── docs
├── .nojekyll
├── 0.1e9b80bcbcd32e2a69d7.bundle.map
├── 0.1e9b80bcbcd32e2a69d7.chunk.js
├── 404.html
├── 674f50d287a8c48dc19ba404d20fe713.eot
├── 7425dac35a7f1437490f3a09d5139d6d.css
├── 912ec66d7572ff821749319396470bde.svg
├── af7ae505a9eed503f8b8e6982036873e.woff2
├── app.2af45f166b6c8f44277a.bundle.js
├── app.2af45f166b6c8f44277a.bundle.map
├── app.86ebb9011c2802314f3c.bundle.js
├── app.86ebb9011c2802314f3c.bundle.map
├── b06871f281fee6b241d60582ae9369b9.ttf
├── common.5d15e7f40f97c1ba30f9.bundle.js
├── common.5d15e7f40f97c1ba30f9.bundle.map
├── common.6009d48c5bbfa39c76c9.bundle.js
├── common.6009d48c5bbfa39c76c9.bundle.map
├── favicon.ico
├── fee66e712a8a08eef5805a46892932ad.woff
├── images
│ ├── aurelia---screen.png
│ ├── aurelia-icon-512x512.png
│ ├── avatar.jpg
│ ├── favicon.ico
│ └── hero.png
├── index.html
├── vendor.23fd87a635a88ae059ed.bundle.js
└── vendor.23fd87a635a88ae059ed.bundle.map
├── firebase.rules.json
├── index.ejs
├── package-lock.json
├── package-scripts.js
├── package.json
├── src
├── app.html
├── app.ts
├── common
│ ├── functions.ts
│ └── interfaces.ts
├── config
│ ├── application.config.json
│ ├── author.config.json
│ ├── firebase.config.json
│ └── social.config.json
├── environment.ts
├── images
│ ├── aurelia---screen.png
│ ├── aurelia-icon-512x512.png
│ ├── avatar.jpg
│ ├── favicon.ico
│ └── hero.png
├── main.ts
├── pages
│ ├── about.html
│ ├── about.ts
│ ├── blog.html
│ ├── blog.ts
│ ├── category.html
│ ├── category.ts
│ ├── contact.html
│ ├── contact.ts
│ ├── home.html
│ ├── home.ts
│ ├── portfolio.html
│ ├── portfolio.ts
│ ├── post.html
│ └── post.ts
├── resources
│ ├── elements
│ │ ├── categories
│ │ │ ├── categories.html
│ │ │ └── categories.ts
│ │ ├── comments
│ │ │ ├── comments.html
│ │ │ └── comments.ts
│ │ ├── nav-bar.html
│ │ ├── profile.html
│ │ └── readingTime
│ │ │ ├── readingTime.html
│ │ │ └── readingTime.ts
│ ├── index.ts
│ └── value-converters
│ │ └── date-format.ts
├── services
│ ├── IBlogPost.ts
│ ├── config.ts
│ ├── dataService.ts
│ ├── markdown.ts
│ └── utilities.ts
├── setup
│ ├── setup.html
│ └── setup.ts
└── styles
│ ├── _aurelia.scss
│ ├── _post.scss
│ ├── _variables.scss
│ ├── normalize.css
│ ├── site.scss
│ ├── theme.js
│ └── theme.scss
├── test
├── jest-pretest.ts
└── unit
│ ├── __snapshots__
│ └── welcome.spec.ts.snap
│ ├── app.spec.ts
│ ├── child-router.spec.ts
│ ├── users.spec.ts
│ └── welcome.spec.ts
├── tsconfig.json
├── tslint.json
└── webpack.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 |
11 | # 2 space indentation
12 | [**.*]
13 | indent_style = space
14 | indent_size = 2
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_STORE
3 | npm-debug.log*
4 | debug.log
5 | /test/*coverage
6 | /test/coverage*
7 | dist
8 | test-data
9 |
10 | ## Team Essentials ##
11 | #####################
12 | .vscode/settings.json
13 | .vscode/team-essentials/state.json
14 | .vscode/user.json
15 |
--------------------------------------------------------------------------------
/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/.nojekyll
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 120,
3 | "semi": true,
4 | "singleQuote": true,
5 | "trailingComma": "none",
6 | "arrowParens": "always",
7 | "proseWrap": "preserve"
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "AureliaEffect.aurelia",
4 | "msjsdiag.debugger-for-chrome",
5 | "steoates.autoimport",
6 | "EditorConfig.EditorConfig",
7 | "christian-kohler.path-intellisense",
8 | "behzad88.Aurelia"
9 | ]
10 | }
--------------------------------------------------------------------------------
/.vscode/team-essentials/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "debug": {
3 | "start": {
4 | "output": "workbench.debug.action.focusRepl",
5 | "explorer": "workbench.view.debug"
6 | },
7 | "stop": {
8 | "output": "workbench.action.output.toggleOutput",
9 | "explorer": "workbench.view.explorer",
10 | "terminatePreLaunchTask": true
11 | }
12 | },
13 | "statusbar": {
14 | "disable": false,
15 | "align": "left",
16 | "hideIcon": false,
17 | "icon": "search",
18 | "priority": 0
19 | }
20 | }
--------------------------------------------------------------------------------
/.vscode/team-essentials/filters.json:
--------------------------------------------------------------------------------
1 | {
2 | "default": {
3 | ".vscode": true,
4 | ".gitignore": true,
5 | "custom_typings": true,
6 | "yarn.lock": true
7 | },
8 | "code": {
9 | "node_modules": true,
10 | "favicon.ico": true,
11 | ".editorconfig": true,
12 | "tsconfig.*": true,
13 | "webpack.*": true,
14 | "wallaby.js": true,
15 | "README.*": true,
16 | "test": true
17 | },
18 | "admin": {
19 | ".editorconfig": true,
20 | "yarn.lock": true
21 | }
22 | }
--------------------------------------------------------------------------------
/.vscode/team-essentials/settings.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/.vscode/team.json:
--------------------------------------------------------------------------------
1 | {
2 | "explorer.filters": {
3 | "default": {
4 | // Config files
5 | ".vscode": true,
6 | ".gitignore": true,
7 | "custom_typings": true,
8 | "yarn.lock": true
9 | },
10 | "code": {
11 | "node_modules": true,
12 | "favicon.ico": true,
13 | ".editorconfig": true,
14 | "tsconfig.*": true,
15 | "webpack.*": true,
16 | "wallaby.js": true,
17 | "README.*": true,
18 | "test": true
19 | },
20 | "admin": {
21 | ".editorconfig": true,
22 | "yarn.lock": true
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/.vscode/user.json:
--------------------------------------------------------------------------------
1 | {
2 | "extensions.required.installed": true,
3 | "explorer.filter": "admin",
4 | "defaults.applied": true
5 | }
--------------------------------------------------------------------------------
/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Single Page Apps for GitHub Pages
6 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/CNAME:
--------------------------------------------------------------------------------
1 | webrenaissance.org
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # blog-skeleton
2 | ## Technologies in this skelton:
3 | * aurelia+typescript 2 (based on the aurelia-webpack-typescript skeleton)
4 | * webpack 2
5 | * bootstrap 4, beta 3
6 | * font-awesome
7 | * firebase (for blog store)
8 |
9 | ## Getting started
10 |
11 | Before you start:
12 |
13 | 1) Make sure you have a recent version of [NodeJS](http://nodejs.org/) environment *>=6.0* with NPM 3 or Yarn.
14 | 2) Create a firebase account and database
15 | 3) Add string to main.ts instance variable 'firebaseRoot'
16 |
17 | ### Starting the blog locally
18 | From the project folder, execute the following commands:
19 |
20 | ```shell
21 | npm install
22 | ```
23 |
24 | This will install all required dependencies, including a local version of Webpack that is going to
25 | build and bundle the app. There is no need to install Webpack globally.
26 |
27 | To run the app execute the following command:
28 |
29 | ```shell
30 | npm start
31 | ```
32 |
33 | This command starts the webpack development server that serves the build bundles.
34 | You can now browse the skeleton app at http://localhost:8080 (or the next available port, notice the output of the command). Changes in the code
35 | will automatically build and reload the app.
36 |
37 | ### Running with Hot Module Reload
38 |
39 | If you wish to try out the experimental Hot Module Reload, you may run your application with the following command:
40 |
41 | ```shell
42 | npm start -- webpack.server.hmr
43 | ```
44 |
45 | ## Feature configuration
46 |
47 | Most of the configuration will happen in the `webpack.config.js` file.
48 | There, you may configure advanced loader features or add direct SASS or LESS loading support.
49 |
50 | ## Bundling
51 |
52 | To build an optimized, minified production bundle (output to /dist) execute:
53 |
54 | ```shell
55 | npm start -- build
56 | ```
57 |
58 | To build
59 |
60 | To test either the development or production build execute:
61 |
62 | ```shell
63 | npm start -- serve
64 | ```
65 |
66 | The production bundle includes all files that are required for deployment.
67 |
68 | ## Running The Tests
69 |
70 | This skeleton provides three frameworks for running tests.
71 |
72 | You can choose one or two and remove the other, or even use all of them for different types of tests.
73 |
74 | By default, both Jest and Karma are configured to run the same tests with Jest's matchers (see Jest documentation for more information).
75 |
76 | If you wish to only run certain tests under one of the runners, wrap them in an `if`, like this:
77 |
78 | ```js
79 | if (jest) {
80 | // since only jest supports creating snapshot:
81 | it('should render correctly', () => {
82 | expect(document.body.outerHTML).toMatchSnapshot();
83 | });
84 | }
85 | ```
86 |
87 | ### Jest + Jasmine 2
88 |
89 | Jest is a powerful unit testing runner and framework.
90 | It runs really fast, however the tests are run under NodeJS, not the browser.
91 | This means there might be some cases where something you'd expect works in reality, but fails in a test. One of those things will be SVG, which isn't supported under NodeJS. However, the framework is perfect for doing unit tests of pure functions, and works pretty well in combination with `aurelia-testing`.
92 |
93 | To create new Jest tests, create files with the extension `.test.ts`, either in the `src` directory or in the `test/jest-unit` directory.
94 |
95 | To run the Jest unit tests, run:
96 |
97 | ```shell
98 | npm test
99 | ```
100 |
101 | To run the Jest watcher (re-runs tests on changes), run:
102 |
103 | ```shell
104 | npm start -- test.jest.watch
105 | ```
106 |
107 | ### Karma + Jasmine
108 |
109 | Karma is also a powerful test runner, and combined with Jasmine it can be a pleasure to work with. Karma always runs in the browser. This means that whatever works in real browsers, should also work the same way in the unit tests. But it also means the framework is heavier to execute and not as lean to work with.
110 |
111 | To create new Karma tests, create files with the extension `.spec.ts`, either in the `src` directory or in the `test/karma-unit` directory.
112 |
113 | To run the Karma unit tests, run:
114 |
115 | ```shell
116 | npm start -- test.karma
117 | ```
118 |
119 | To run the Karma watcher (re-runs tests on changes), run:
120 |
121 | ```shell
122 | npm start -- test.karma.watch
123 | ```
124 |
125 | ### Protractor (E2E / integration tests)
126 |
127 | Integration tests can be performed with [Protractor](http://angular.github.io/protractor/#/).
128 |
129 | 1. Place your E2E-Tests into the folder ```test/e2e``` and name them with the extension `.e2e.ts`.
130 |
131 | 2. Run the tests by invoking
132 |
133 | ```shell
134 | npm start -- e2e
135 | ```
136 |
137 | ## Running all test suites
138 |
139 | To run all the unit test suites and the E2E tests, you may simply run:
140 |
141 | ```shell
142 | npm start -- test.all
143 | ```
144 |
--------------------------------------------------------------------------------
/aurelia_project/aurelia.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blog",
3 | "type": "project:application",
4 | "bundler": {
5 | "id": "webpack",
6 | "displayName": "Webpack"
7 | },
8 | "build": {
9 | "options": {
10 | "server": "dev",
11 | "extractCss": "prod",
12 | "coverage": false
13 | }
14 | },
15 | "platform": {
16 | "id": "web",
17 | "displayName": "Web",
18 | "port": 8080,
19 | "hmr": false,
20 | "open": false,
21 | "output": "docs"
22 | },
23 | "loader": {
24 | "id": "none",
25 | "displayName": "None"
26 | },
27 | "transpiler": {
28 | "id": "typescript",
29 | "displayName": "TypeScript",
30 | "fileExtension": ".ts"
31 | },
32 | "markupProcessor": {
33 | "id": "none",
34 | "displayName": "None",
35 | "fileExtension": ".html"
36 | },
37 | "cssProcessor": {
38 | "id": "sass",
39 | "displayName": "Sass",
40 | "fileExtension": ".scss"
41 | },
42 | "editor": {
43 | "id": "vscode",
44 | "displayName": "Visual Studio Code"
45 | },
46 | "unitTestRunner": [
47 | {
48 | "id": "jest",
49 | "displayName": "Jest"
50 | }
51 | ],
52 | "integrationTestRunner": {
53 | "id": "none",
54 | "displayName": "None"
55 | },
56 | "paths": {
57 | "root": "src",
58 | "resources": "resources",
59 | "elements": "resources/elements",
60 | "attributes": "resources/attributes",
61 | "valueConverters": "resources/value-converters",
62 | "bindingBehaviors": "resources/binding-behaviors"
63 | },
64 | "testFramework": {
65 | "id": "jasmine",
66 | "displayName": "Jasmine"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/aurelia_project/environments/dev.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | debug: true,
3 | testing: true
4 | };
5 |
--------------------------------------------------------------------------------
/aurelia_project/environments/prod.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | debug: false,
3 | testing: false
4 | };
5 |
--------------------------------------------------------------------------------
/aurelia_project/environments/stage.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | debug: true,
3 | testing: false
4 | };
5 |
--------------------------------------------------------------------------------
/aurelia_project/generators/attribute.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "attribute",
3 | "description": "Creates a custom attribute class and places it in the project resources."
4 | }
5 |
--------------------------------------------------------------------------------
/aurelia_project/generators/attribute.ts:
--------------------------------------------------------------------------------
1 | import {inject} from 'aurelia-dependency-injection';
2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';
3 |
4 | @inject(Project, CLIOptions, UI)
5 | export default class AttributeGenerator {
6 | constructor(private project: Project, private options: CLIOptions, private ui: UI) { }
7 |
8 | execute() {
9 | return this.ui
10 | .ensureAnswer(this.options.args[0], 'What would you like to call the custom attribute?')
11 | .then(name => {
12 | let fileName = this.project.makeFileName(name);
13 | let className = this.project.makeClassName(name);
14 |
15 | this.project.attributes.add(
16 | ProjectItem.text(`${fileName}.ts`, this.generateSource(className))
17 | );
18 |
19 | return this.project.commitChanges()
20 | .then(() => this.ui.log(`Created ${fileName}.`));
21 | });
22 | }
23 |
24 | generateSource(className) {
25 | return `import {autoinject} from 'aurelia-framework';
26 |
27 | @autoinject()
28 | export class ${className}CustomAttribute {
29 | constructor(private element: Element) { }
30 |
31 | valueChanged(newValue, oldValue) {
32 |
33 | }
34 | }
35 |
36 | `
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/aurelia_project/generators/binding-behavior.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "binding-behavior",
3 | "description": "Creates a binding behavior class and places it in the project resources."
4 | }
5 |
--------------------------------------------------------------------------------
/aurelia_project/generators/binding-behavior.ts:
--------------------------------------------------------------------------------
1 | import {inject} from 'aurelia-dependency-injection';
2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';
3 |
4 | @inject(Project, CLIOptions, UI)
5 | export default class BindingBehaviorGenerator {
6 | constructor(private project: Project, private options: CLIOptions, private ui: UI) { }
7 |
8 | execute() {
9 | return this.ui
10 | .ensureAnswer(this.options.args[0], 'What would you like to call the binding behavior?')
11 | .then(name => {
12 | let fileName = this.project.makeFileName(name);
13 | let className = this.project.makeClassName(name);
14 |
15 | this.project.bindingBehaviors.add(
16 | ProjectItem.text(`${fileName}.ts`, this.generateSource(className))
17 | );
18 |
19 | return this.project.commitChanges()
20 | .then(() => this.ui.log(`Created ${fileName}.`));
21 | });
22 | }
23 |
24 | generateSource(className) {
25 | return `export class ${className}BindingBehavior {
26 | bind(binding, source) {
27 |
28 | }
29 |
30 | unbind(binding, source) {
31 |
32 | }
33 | }
34 |
35 | `
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/aurelia_project/generators/component.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "component",
3 | "description": "Creates a custom component class and template (view model and view), placing them in the project source folder (or optionally in sub folders)."
4 | }
--------------------------------------------------------------------------------
/aurelia_project/generators/component.ts:
--------------------------------------------------------------------------------
1 | import { inject } from 'aurelia-dependency-injection';
2 | import { Project, ProjectItem, CLIOptions, UI } from 'aurelia-cli';
3 |
4 | var path = require('path');
5 |
6 | @inject(Project, CLIOptions, UI)
7 | export default class ElementGenerator {
8 | constructor(private project: Project, private options: CLIOptions, private ui: UI) { }
9 |
10 | execute() {
11 | let self = this;
12 |
13 | return this.ui
14 | .ensureAnswer(this.options.args[0], 'What would you like to call the component?')
15 | .then(name => {
16 |
17 | return self.ui.ensureAnswer(this.options.args[1], 'What sub-folder would you like to add it to?\nIf it doesn\'t exist it will be created for you.\n\nDefault folder is the source folder (src).', ".")
18 | .then(subFolders => {
19 |
20 | let fileName = this.project.makeFileName(name);
21 | let className = this.project.makeClassName(name);
22 |
23 | self.project.root.add(
24 | ProjectItem.text(path.join(subFolders, fileName + ".ts"), this.generateJSSource(className)),
25 | ProjectItem.text(path.join(subFolders, fileName + ".html"), this.generateHTMLSource(className))
26 | );
27 |
28 | return this.project.commitChanges()
29 | .then(() => this.ui.log(`Created ${name} in the '${path.join(self.project.root.name, subFolders)}' folder`));
30 | });
31 | });
32 | }
33 |
34 | generateJSSource(className) {
35 | return `export class ${className} {
36 | message: string;
37 |
38 | constructor() {
39 | this.message = 'Hello world';
40 | }
41 | }`
42 | }
43 |
44 | generateHTMLSource(className) {
45 | return `
46 | \${message}
47 | `
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/aurelia_project/generators/element.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "element",
3 | "description": "Creates a custom element class and template, placing them in the project resources."
4 | }
5 |
--------------------------------------------------------------------------------
/aurelia_project/generators/element.ts:
--------------------------------------------------------------------------------
1 | import {inject} from 'aurelia-dependency-injection';
2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';
3 |
4 | @inject(Project, CLIOptions, UI)
5 | export default class ElementGenerator {
6 | constructor(private project: Project, private options: CLIOptions, private ui: UI) { }
7 |
8 | execute() {
9 | return this.ui
10 | .ensureAnswer(this.options.args[0], 'What would you like to call the custom element?')
11 | .then(name => {
12 | let fileName = this.project.makeFileName(name);
13 | let className = this.project.makeClassName(name);
14 |
15 | this.project.elements.add(
16 | ProjectItem.text(`${fileName}.ts`, this.generateJSSource(className)),
17 | ProjectItem.text(`${fileName}.html`, this.generateHTMLSource(className))
18 | );
19 |
20 | return this.project.commitChanges()
21 | .then(() => this.ui.log(`Created ${fileName}.`));
22 | });
23 | }
24 |
25 | generateJSSource(className) {
26 | return `import {bindable} from 'aurelia-framework';
27 |
28 | export class ${className} {
29 | @bindable value;
30 |
31 | valueChanged(newValue, oldValue) {
32 |
33 | }
34 | }
35 |
36 | `
37 | }
38 |
39 | generateHTMLSource(className) {
40 | return `
41 | \${value}
42 | `
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/aurelia_project/generators/generator.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "generator",
3 | "description": "Creates a generator class and places it in the project generators folder."
4 | }
5 |
--------------------------------------------------------------------------------
/aurelia_project/generators/generator.ts:
--------------------------------------------------------------------------------
1 | import {inject} from 'aurelia-dependency-injection';
2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';
3 |
4 | @inject(Project, CLIOptions, UI)
5 | export default class GeneratorGenerator {
6 | constructor(private project: Project, private options: CLIOptions, private ui: UI) { }
7 |
8 | execute() {
9 | return this.ui
10 | .ensureAnswer(this.options.args[0], 'What would you like to call the generator?')
11 | .then(name => {
12 | let fileName = this.project.makeFileName(name);
13 | let className = this.project.makeClassName(name);
14 |
15 | this.project.generators.add(
16 | ProjectItem.text(`${fileName}.ts`, this.generateSource(className))
17 | );
18 |
19 | return this.project.commitChanges()
20 | .then(() => this.ui.log(`Created ${fileName}.`));
21 | });
22 | }
23 |
24 | generateSource(className) {
25 | return `import {autoinject} from 'aurelia-dependency-injection';
26 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';
27 |
28 | @autoinject()
29 | export default class ${className}Generator {
30 | constructor(private project: Project, private options: CLIOptions, private ui: UI) { }
31 |
32 | execute() {
33 | return this.ui
34 | .ensureAnswer(this.options.args[0], 'What would you like to call the new item?')
35 | .then(name => {
36 | let fileName = this.project.makeFileName(name);
37 | let className = this.project.makeClassName(name);
38 |
39 | this.project.elements.add(
40 | ProjectItem.text(\`\${fileName}.ts\`, this.generateSource(className))
41 | );
42 |
43 | return this.project.commitChanges()
44 | .then(() => this.ui.log(\`Created \${fileName}.\`));
45 | });
46 | }
47 |
48 | generateSource(className) {
49 | return \`import {bindable} from 'aurelia-framework';
50 |
51 | export class \${className} {
52 | @bindable value;
53 |
54 | valueChanged(newValue, oldValue) {
55 |
56 | }
57 | }
58 |
59 | \`
60 | }
61 | }
62 |
63 | `
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/aurelia_project/generators/task.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "task",
3 | "description": "Creates a task and places it in the project tasks folder."
4 | }
5 |
--------------------------------------------------------------------------------
/aurelia_project/generators/task.ts:
--------------------------------------------------------------------------------
1 | import {inject} from 'aurelia-dependency-injection';
2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';
3 |
4 | @inject(Project, CLIOptions, UI)
5 | export default class TaskGenerator {
6 | constructor(private project: Project, private options: CLIOptions, private ui: UI) { }
7 |
8 | execute() {
9 | return this.ui
10 | .ensureAnswer(this.options.args[0], 'What would you like to call the task?')
11 | .then(name => {
12 | let fileName = this.project.makeFileName(name);
13 | let functionName = this.project.makeFunctionName(name);
14 |
15 | this.project.tasks.add(
16 | ProjectItem.text(`${fileName}.ts`, this.generateSource(functionName))
17 | );
18 |
19 | return this.project.commitChanges()
20 | .then(() => this.ui.log(`Created ${fileName}.`));
21 | });
22 | }
23 |
24 | generateSource(functionName) {
25 | return `import * as gulp from 'gulp';
26 | import * as changed from 'gulp-changed';
27 | import * as project from '../aurelia.json';
28 |
29 | export default function ${functionName}() {
30 | return gulp.src(project.paths.???)
31 | .pipe(changed(project.paths.output, {extension: '.???'}))
32 | .pipe(gulp.dest(project.paths.output));
33 | }
34 |
35 | `
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/aurelia_project/generators/value-converter.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "value-converter",
3 | "description": "Creates a value converter class and places it in the project resources."
4 | }
5 |
--------------------------------------------------------------------------------
/aurelia_project/generators/value-converter.ts:
--------------------------------------------------------------------------------
1 | import {inject} from 'aurelia-dependency-injection';
2 | import {Project, ProjectItem, CLIOptions, UI} from 'aurelia-cli';
3 |
4 | @inject(Project, CLIOptions, UI)
5 | export default class ValueConverterGenerator {
6 | constructor(private project: Project, private options: CLIOptions, private ui: UI) { }
7 |
8 | execute() {
9 | return this.ui
10 | .ensureAnswer(this.options.args[0], 'What would you like to call the value converter?')
11 | .then(name => {
12 | let fileName = this.project.makeFileName(name);
13 | let className = this.project.makeClassName(name);
14 |
15 | this.project.valueConverters.add(
16 | ProjectItem.text(`${fileName}.ts`, this.generateSource(className))
17 | );
18 |
19 | return this.project.commitChanges()
20 | .then(() => this.ui.log(`Created ${fileName}.`));
21 | });
22 | }
23 |
24 | generateSource(className) {
25 | return `export class ${className}ValueConverter {
26 | toView(value) {
27 |
28 | }
29 |
30 | fromView(value) {
31 |
32 | }
33 | }
34 |
35 | `
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/aurelia_project/tasks/build.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "build",
3 | "description": "Builds and processes all application assets.",
4 | "flags": [
5 | {
6 | "name": "env",
7 | "description": "Sets the build environment.",
8 | "type": "string"
9 | },
10 | {
11 | "name": "watch",
12 | "description": "Watches source files for changes and refreshes the bundles automatically.",
13 | "type": "boolean"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/aurelia_project/tasks/build.ts:
--------------------------------------------------------------------------------
1 | import * as webpackConfig from "../../webpack.config";
2 | import * as webpack from "webpack";
3 | let project = require("../aurelia.json");
4 | import { CLIOptions, Configuration } from "aurelia-cli";
5 | import * as gulp from "gulp";
6 | import configureEnvironment from "./environment";
7 | import * as del from "del";
8 |
9 | const buildOptions = new Configuration(project.build.options);
10 | const production = CLIOptions.getEnvironment() === "prod";
11 | const server = buildOptions.isApplicable("server");
12 | const extractCss = buildOptions.isApplicable("extractCss");
13 | const coverage = buildOptions.isApplicable("coverage");
14 |
15 | const config = webpackConfig({
16 | production,
17 | server,
18 | extractCss,
19 | coverage
20 | });
21 | const compiler = webpack(config);
22 |
23 | function buildWebpack(done) {
24 | if (CLIOptions.hasFlag("watch")) {
25 | compiler.watch({}, onBuild);
26 | } else {
27 | compiler.run(onBuild);
28 | compiler.plugin("done", () => done());
29 | }
30 | }
31 |
32 | function onBuild(err, stats) {
33 | if (err) {
34 | console.error(err.stack || err);
35 | if (err.details) console.error(err.details);
36 | process.exit(1);
37 | } else {
38 | process.stdout.write(stats.toString({ colors: require("supports-color") }) + "\n");
39 | }
40 | }
41 |
42 | function clearDist() {
43 | return del([config.output.path]);
44 | }
45 |
46 | const build = gulp.series(clearDist, configureEnvironment, buildWebpack);
47 |
48 | export { config, buildWebpack, build as default };
49 |
--------------------------------------------------------------------------------
/aurelia_project/tasks/environment.ts:
--------------------------------------------------------------------------------
1 | let project = require("../aurelia.json");
2 | import * as rename from "gulp-rename";
3 | import { CLIOptions } from "aurelia-cli";
4 | import * as gulp from "gulp";
5 | import * as fs from "fs";
6 | import * as path from "path";
7 | import * as through from "through2";
8 |
9 | function configureEnvironment() {
10 | let env = CLIOptions.getEnvironment();
11 |
12 | return gulp
13 | .src(`aurelia_project/environments/${env}${project.transpiler.fileExtension}`)
14 | .pipe(rename(`environment${project.transpiler.fileExtension}`))
15 | .pipe(gulp.dest(project.paths.root))
16 | .pipe(
17 | through.obj(function(file, enc, cb) {
18 | // https://github.com/webpack/watchpack/issues/25#issuecomment-287789288
19 | var now = Date.now() / 1000;
20 | var then = now - 10;
21 | fs.utimes(file.path, then, then, function(err) {
22 | if (err) throw err;
23 | });
24 | cb(null, file);
25 | })
26 | );
27 | }
28 |
29 | export default configureEnvironment;
30 |
--------------------------------------------------------------------------------
/aurelia_project/tasks/jest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jest",
3 | "description": "Runs Jest and reports the results.",
4 | "parameters": [
5 | {
6 | "name": "watch",
7 | "description": "Watches test files for changes and re-runs the tests automatically.",
8 | "type": "boolean"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/aurelia_project/tasks/jest.ts:
--------------------------------------------------------------------------------
1 | import * as jest from "jest-cli";
2 | import * as gutil from "gulp-util";
3 | import through2 from "through2";
4 | import * as path from "path";
5 | let packageJson = require("../../package.json");
6 |
7 | import { CLIOptions } from "aurelia-cli";
8 |
9 | export default cb => {
10 | let options = packageJson.jest;
11 |
12 | if (CLIOptions.hasFlag("watch")) {
13 | Object.assign(options, { watch: true });
14 | }
15 |
16 | jest.runCLI(options, [path.resolve(__dirname, "../../")], result => {
17 | if (result.numFailedTests || result.numFailedTestSuites) {
18 | cb(new gutil.PluginError("gulp-jest", { message: "Tests Failed" }));
19 | } else {
20 | cb();
21 | }
22 | });
23 | };
24 |
--------------------------------------------------------------------------------
/aurelia_project/tasks/run.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "run",
3 | "description": "Builds the application and serves up the assets via a local web server, watching files for changes as you work.",
4 | "flags": [
5 | {
6 | "name": "env",
7 | "description": "Sets the build environment.",
8 | "type": "string"
9 | },
10 | {
11 | "name": "watch",
12 | "description": "Watches source files for changes and refreshes the app automatically.",
13 | "type": "boolean"
14 | },
15 | {
16 | "name": "hmr",
17 | "description": "Enable Hot Module Reload",
18 | "type": "boolean"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/aurelia_project/tasks/run.ts:
--------------------------------------------------------------------------------
1 | import { config } from "./build";
2 | import configureEnvironment from "./environment";
3 | import * as webpack from "webpack";
4 | import * as Server from "webpack-dev-server";
5 | let project = require("../aurelia.json");
6 | import { CLIOptions, reportWebpackReadiness } from "aurelia-cli";
7 | import * as gulp from "gulp";
8 | import { buildWebpack } from "./build";
9 |
10 | function runWebpack(done) {
11 | // https://webpack.github.io/docs/webpack-dev-server.html
12 | let opts = {
13 | host: "localhost",
14 | publicPath: config.output.publicPath,
15 | filename: config.output.filename,
16 | hot: project.platform.hmr || CLIOptions.hasFlag("hmr"),
17 | port: project.platform.port,
18 | contentBase: config.output.path,
19 | historyApiFallback: true,
20 | open: project.platform.open,
21 | stats: {
22 | colors: require("supports-color")
23 | }
24 | } as any;
25 |
26 | if (!CLIOptions.hasFlag("watch")) {
27 | opts.lazy = true;
28 | }
29 |
30 | if (project.platform.hmr || CLIOptions.hasFlag("hmr")) {
31 | config.plugins.push(new webpack.HotModuleReplacementPlugin());
32 | config.entry.app.unshift(`webpack-dev-server/client?http://${opts.host}:${opts.port}/`, "webpack/hot/dev-server");
33 | }
34 |
35 | const compiler = webpack(config);
36 | let server = new Server(compiler, opts);
37 |
38 | server.listen(opts.port, opts.host, function(err) {
39 | if (err) throw err;
40 |
41 | if (opts.lazy) {
42 | buildWebpack(() => {
43 | reportWebpackReadiness(opts);
44 | done();
45 | });
46 | } else {
47 | reportWebpackReadiness(opts);
48 | done();
49 | }
50 | });
51 | }
52 |
53 | const run = gulp.series(configureEnvironment, runWebpack);
54 |
55 | export { run as default };
56 |
--------------------------------------------------------------------------------
/custom_typings/fetch.d.ts:
--------------------------------------------------------------------------------
1 | declare module "isomorphic-fetch" {
2 | export = fetch;
3 | }
--------------------------------------------------------------------------------
/custom_typings/system.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'system' {
2 | import fetch = require('isomorphic-fetch');
3 | import * as Aurelia from 'aurelia-framework';
4 |
5 | /*
6 | * List your dynamically imported modules to get typing support
7 | */
8 | interface System {
9 | import(name: string): Promise;
10 | import(name: 'aurelia-framework'): Promise;
11 | import(name: 'isomorphic-fetch'): Promise;
12 | }
13 |
14 | global {
15 | var System: System;
16 | }
17 | }
--------------------------------------------------------------------------------
/custom_typings/window.d.ts:
--------------------------------------------------------------------------------
1 | interface Window {
2 | firebase: any;
3 | }
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/0.1e9b80bcbcd32e2a69d7.bundle.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["webpack:///0.1e9b80bcbcd32e2a69d7.chunk.js","webpack:///./node_modules/isomorphic-fetch/fetch-npm-browserify.js","webpack:///./node_modules/whatwg-fetch/fetch.js"],"names":["webpackJsonp","612","module","exports","__webpack_require__","self","fetch","bind","613","Promise","normalizeName","name","String","test","TypeError","toLowerCase","normalizeValue","value","iteratorFor","items","iterator","next","shift","done","support","iterable","Symbol","Headers","headers","this","map","forEach","append","Array","isArray","header","Object","getOwnPropertyNames","consumed","body","bodyUsed","reject","fileReaderReady","reader","resolve","onload","result","onerror","error","readBlobAsArrayBuffer","blob","FileReader","promise","readAsArrayBuffer","readBlobAsText","readAsText","readArrayBufferAsText","buf","view","Uint8Array","chars","length","i","fromCharCode","join","bufferClone","slice","byteLength","set","buffer","Body","_initBody","_bodyInit","_bodyText","Blob","prototype","isPrototypeOf","_bodyBlob","formData","FormData","_bodyFormData","searchParams","URLSearchParams","toString","arrayBuffer","isDataView","_bodyArrayBuffer","ArrayBuffer","isArrayBufferView","Error","get","type","rejected","then","text","decode","json","JSON","parse","normalizeMethod","method","upcased","toUpperCase","methods","indexOf","Request","input","options","url","credentials","mode","referrer","form","trim","split","bytes","replace","decodeURIComponent","parseHeaders","rawHeaders","line","parts","key","Response","bodyInit","status","ok","statusText","e","viewClasses","obj","DataView","isView","call","oldValue","delete","has","hasOwnProperty","callback","thisArg","keys","push","values","entries","clone","response","redirectStatuses","redirect","RangeError","location","init","request","xhr","XMLHttpRequest","getAllResponseHeaders","responseURL","responseText","ontimeout","open","withCredentials","responseType","setRequestHeader","send","polyfill"],"mappings":"AAAAA,cAAc,IAERC,IACA,SAAUC,EAAQC,EAASC,GCCjCA,EAAA,KACAF,EAAAC,QAAAE,KAAAC,MAAAC,KAAAF,ODUMG,IACA,SAAUN,EAAQC,EAASC,aEhBjCK,IAAA,SAAAJ,GACA,YA2CA,SAAAK,GAAAC,GAIA,GAHA,gBAAAA,KACAA,EAAAC,OAAAD,IAEA,6BAAAE,KAAAF,GACA,SAAAG,WAAA,yCAEA,OAAAH,GAAAI,cAGA,QAAAC,GAAAC,GAIA,MAHA,gBAAAA,KACAA,EAAAL,OAAAK,IAEAA,EAIA,QAAAC,GAAAC,GACA,GAAAC,IACAC,KAAA,WACA,GAAAJ,GAAAE,EAAAG,OACA,QAAgBC,cAAAN,YAUhB,OANAO,GAAAC,WACAL,EAAAM,OAAAN,UAAA,WACA,MAAAA,KAIAA,EAGA,QAAAO,GAAAC,GACAC,KAAAC,OAEAF,YAAAD,GACAC,EAAAG,QAAA,SAAAd,EAAAN,GACAkB,KAAAG,OAAArB,EAAAM,IACOY,MACFI,MAAAC,QAAAN,GACLA,EAAAG,QAAA,SAAAI,GACAN,KAAAG,OAAAG,EAAA,GAAAA,EAAA,KACON,MACFD,GACLQ,OAAAC,oBAAAT,GAAAG,QAAA,SAAApB,GACAkB,KAAAG,OAAArB,EAAAiB,EAAAjB,KACOkB,MA0DP,QAAAS,GAAAC,GACA,GAAAA,EAAAC,SACA,MAAA/B,GAAAgC,OAAA,GAAA3B,WAAA,gBAEAyB,GAAAC,YAGA,QAAAE,GAAAC,GACA,UAAAlC,GAAA,SAAAmC,EAAAH,GACAE,EAAAE,OAAA,WACAD,EAAAD,EAAAG,SAEAH,EAAAI,QAAA,WACAN,EAAAE,EAAAK,UAKA,QAAAC,GAAAC,GACA,GAAAP,GAAA,GAAAQ,YACAC,EAAAV,EAAAC,EAEA,OADAA,GAAAU,kBAAAH,GACAE,EAGA,QAAAE,GAAAJ,GACA,GAAAP,GAAA,GAAAQ,YACAC,EAAAV,EAAAC,EAEA,OADAA,GAAAY,WAAAL,GACAE,EAGA,QAAAI,GAAAC,GAIA,OAHAC,GAAA,GAAAC,YAAAF,GACAG,EAAA,GAAA3B,OAAAyB,EAAAG,QAEAC,EAAA,EAAmBA,EAAAJ,EAAAG,OAAiBC,IACpCF,EAAAE,GAAAlD,OAAAmD,aAAAL,EAAAI,GAEA,OAAAF,GAAAI,KAAA,IAGA,QAAAC,GAAAR,GACA,GAAAA,EAAAS,MACA,MAAAT,GAAAS,MAAA,EAEA,IAAAR,GAAA,GAAAC,YAAAF,EAAAU,WAEA,OADAT,GAAAU,IAAA,GAAAT,YAAAF,IACAC,EAAAW,OAIA,QAAAC,KA0FA,MAzFAzC,MAAAW,YAEAX,KAAA0C,UAAA,SAAAhC,GAEA,GADAV,KAAA2C,UAAAjC,EACAA,EAEO,mBAAAA,GACPV,KAAA4C,UAAAlC,MACO,IAAAf,EAAA0B,MAAAwB,KAAAC,UAAAC,cAAArC,GACPV,KAAAgD,UAAAtC,MACO,IAAAf,EAAAsD,UAAAC,SAAAJ,UAAAC,cAAArC,GACPV,KAAAmD,cAAAzC,MACO,IAAAf,EAAAyD,cAAAC,gBAAAP,UAAAC,cAAArC,GACPV,KAAA4C,UAAAlC,EAAA4C,eACO,IAAA3D,EAAA4D,aAAA5D,EAAA0B,MAAAmC,EAAA9C,GACPV,KAAAyD,iBAAArB,EAAA1B,EAAA8B,QAEAxC,KAAA2C,UAAA,GAAAE,OAAA7C,KAAAyD,uBACO,KAAA9D,EAAA4D,cAAAG,YAAAZ,UAAAC,cAAArC,KAAAiD,EAAAjD,GAGP,SAAAkD,OAAA,4BAFA5D,MAAAyD,iBAAArB,EAAA1B,OAdAV,MAAA4C,UAAA,EAmBA5C,MAAAD,QAAA8D,IAAA,kBACA,gBAAAnD,GACAV,KAAAD,QAAAwC,IAAA,2CACSvC,KAAAgD,WAAAhD,KAAAgD,UAAAc,KACT9D,KAAAD,QAAAwC,IAAA,eAAAvC,KAAAgD,UAAAc,MACSnE,EAAAyD,cAAAC,gBAAAP,UAAAC,cAAArC,IACTV,KAAAD,QAAAwC,IAAA,oEAKA5C,EAAA0B,OACArB,KAAAqB,KAAA,WACA,GAAA0C,GAAAtD,EAAAT,KACA,IAAA+D,EACA,MAAAA,EAGA,IAAA/D,KAAAgD,UACA,MAAApE,GAAAmC,QAAAf,KAAAgD,UACS,IAAAhD,KAAAyD,iBACT,MAAA7E,GAAAmC,QAAA,GAAA8B,OAAA7C,KAAAyD,mBACS,IAAAzD,KAAAmD,cACT,SAAAS,OAAA,uCAEA,OAAAhF,GAAAmC,QAAA,GAAA8B,OAAA7C,KAAA4C,cAIA5C,KAAAuD,YAAA,WACA,MAAAvD,MAAAyD,iBACAhD,EAAAT,OAAApB,EAAAmC,QAAAf,KAAAyD,kBAEAzD,KAAAqB,OAAA2C,KAAA5C,KAKApB,KAAAiE,KAAA,WACA,GAAAF,GAAAtD,EAAAT,KACA,IAAA+D,EACA,MAAAA,EAGA,IAAA/D,KAAAgD,UACA,MAAAvB,GAAAzB,KAAAgD,UACO,IAAAhD,KAAAyD,iBACP,MAAA7E,GAAAmC,QAAAY,EAAA3B,KAAAyD,kBACO,IAAAzD,KAAAmD,cACP,SAAAS,OAAA,uCAEA,OAAAhF,GAAAmC,QAAAf,KAAA4C,YAIAjD,EAAAsD,WACAjD,KAAAiD,SAAA,WACA,MAAAjD,MAAAiE,OAAAD,KAAAE,KAIAlE,KAAAmE,KAAA,WACA,MAAAnE,MAAAiE,OAAAD,KAAAI,KAAAC,QAGArE,KAMA,QAAAsE,GAAAC,GACA,GAAAC,GAAAD,EAAAE,aACA,OAAAC,GAAAC,QAAAH,IAAA,EAAAA,EAAAD,EAGA,QAAAK,GAAAC,EAAAC,GACAA,OACA,IAAApE,GAAAoE,EAAApE,IAEA,IAAAmE,YAAAD,GAAA,CACA,GAAAC,EAAAlE,SACA,SAAA1B,WAAA,eAEAe,MAAA+E,IAAAF,EAAAE,IACA/E,KAAAgF,YAAAH,EAAAG,YACAF,EAAA/E,UACAC,KAAAD,QAAA,GAAAD,GAAA+E,EAAA9E,UAEAC,KAAAuE,OAAAM,EAAAN,OACAvE,KAAAiF,KAAAJ,EAAAI,KACAvE,GAAA,MAAAmE,EAAAlC,YACAjC,EAAAmE,EAAAlC,UACAkC,EAAAlE,iBAGAX,MAAA+E,IAAAhG,OAAA8F,EAWA,IARA7E,KAAAgF,YAAAF,EAAAE,aAAAhF,KAAAgF,aAAA,QACAF,EAAA/E,SAAAC,KAAAD,UACAC,KAAAD,QAAA,GAAAD,GAAAgF,EAAA/E,UAEAC,KAAAuE,OAAAD,EAAAQ,EAAAP,QAAAvE,KAAAuE,QAAA,OACAvE,KAAAiF,KAAAH,EAAAG,MAAAjF,KAAAiF,MAAA,KACAjF,KAAAkF,SAAA,MAEA,QAAAlF,KAAAuE,QAAA,SAAAvE,KAAAuE,SAAA7D,EACA,SAAAzB,WAAA,4CAEAe,MAAA0C,UAAAhC,GAOA,QAAAwD,GAAAxD,GACA,GAAAyE,GAAA,GAAAjC,SASA,OARAxC,GAAA0E,OAAAC,MAAA,KAAAnF,QAAA,SAAAoF,GACA,GAAAA,EAAA,CACA,GAAAD,GAAAC,EAAAD,MAAA,KACAvG,EAAAuG,EAAA5F,QAAA8F,QAAA,WACAnG,EAAAiG,EAAAlD,KAAA,KAAAoD,QAAA,UACAJ,GAAAhF,OAAAqF,mBAAA1G,GAAA0G,mBAAApG,OAGA+F,EAGA,QAAAM,GAAAC,GACA,GAAA3F,GAAA,GAAAD,EASA,OARA4F,GAAAL,MAAA,SAAAnF,QAAA,SAAAyF,GACA,GAAAC,GAAAD,EAAAN,MAAA,KACAQ,EAAAD,EAAAnG,QAAA2F,MACA,IAAAS,EAAA,CACA,GAAAzG,GAAAwG,EAAAzD,KAAA,KAAAiD,MACArF,GAAAI,OAAA0F,EAAAzG,MAGAW,EAKA,QAAA+F,GAAAC,EAAAjB,GACAA,IACAA,MAGA9E,KAAA8D,KAAA,UACA9D,KAAAgG,OAAA,UAAAlB,KAAAkB,OAAA,IACAhG,KAAAiG,GAAAjG,KAAAgG,QAAA,KAAAhG,KAAAgG,OAAA,IACAhG,KAAAkG,WAAA,cAAApB,KAAAoB,WAAA,KACAlG,KAAAD,QAAA,GAAAD,GAAAgF,EAAA/E,SACAC,KAAA+E,IAAAD,EAAAC,KAAA,GACA/E,KAAA0C,UAAAqD,GA7XA,IAAAvH,EAAAC,MAAA,CAIA,GAAAkB,IACAyD,aAAA,mBAAA5E,GACAoB,SAAA,UAAApB,IAAA,YAAAqB,QACAwB,KAAA,cAAA7C,IAAA,QAAAA,IAAA,WACA,IAEA,MADA,IAAAqE,SAEO,MAAAsD,GACP,aAGAlD,SAAA,YAAAzE,GACA+E,YAAA,eAAA/E,GAGA,IAAAmB,EAAA4D,YACA,GAAA6C,IACA,qBACA,sBACA,6BACA,sBACA,uBACA,sBACA,uBACA,wBACA,yBAGA5C,EAAA,SAAA6C,GACA,MAAAA,IAAAC,SAAAxD,UAAAC,cAAAsD,IAGA1C,EAAAD,YAAA6C,QAAA,SAAAF,GACA,MAAAA,IAAAD,EAAAzB,QAAApE,OAAAuC,UAAAQ,SAAAkD,KAAAH,KAAA,EAyDAvG,GAAAgD,UAAA3C,OAAA,SAAArB,EAAAM,GACAN,EAAAD,EAAAC,GACAM,EAAAD,EAAAC,EACA,IAAAqH,GAAAzG,KAAAC,IAAAnB,EACAkB,MAAAC,IAAAnB,GAAA2H,IAAA,IAAArH,KAGAU,EAAAgD,UAAA4D,OAAA,SAAA5H,SACAkB,MAAAC,IAAApB,EAAAC,KAGAgB,EAAAgD,UAAAe,IAAA,SAAA/E,GAEA,MADAA,GAAAD,EAAAC,GACAkB,KAAA2G,IAAA7H,GAAAkB,KAAAC,IAAAnB,GAAA,MAGAgB,EAAAgD,UAAA6D,IAAA,SAAA7H,GACA,MAAAkB,MAAAC,IAAA2G,eAAA/H,EAAAC,KAGAgB,EAAAgD,UAAAP,IAAA,SAAAzD,EAAAM,GACAY,KAAAC,IAAApB,EAAAC,IAAAK,EAAAC,IAGAU,EAAAgD,UAAA5C,QAAA,SAAA2G,EAAAC,GACA,OAAAhI,KAAAkB,MAAAC,IACAD,KAAAC,IAAA2G,eAAA9H,IACA+H,EAAAL,KAAAM,EAAA9G,KAAAC,IAAAnB,KAAAkB,OAKAF,EAAAgD,UAAAiE,KAAA,WACA,GAAAzH,KAEA,OADAU,MAAAE,QAAA,SAAAd,EAAAN,GAAwCQ,EAAA0H,KAAAlI,KACxCO,EAAAC,IAGAQ,EAAAgD,UAAAmE,OAAA,WACA,GAAA3H,KAEA,OADAU,MAAAE,QAAA,SAAAd,GAAkCE,EAAA0H,KAAA5H,KAClCC,EAAAC,IAGAQ,EAAAgD,UAAAoE,QAAA,WACA,GAAA5H,KAEA,OADAU,MAAAE,QAAA,SAAAd,EAAAN,GAAwCQ,EAAA0H,MAAAlI,EAAAM,MACxCC,EAAAC,IAGAK,EAAAC,WACAE,EAAAgD,UAAAjD,OAAAN,UAAAO,EAAAgD,UAAAoE,QAqJA,IAAAxC,IAAA,6CA4CAE,GAAA9B,UAAAqE,MAAA,WACA,UAAAvC,GAAA5E,MAA8BU,KAAAV,KAAA2C,aA6B9BF,EAAA+D,KAAA5B,EAAA9B,WAgBAL,EAAA+D,KAAAV,EAAAhD,WAEAgD,EAAAhD,UAAAqE,MAAA,WACA,UAAArB,GAAA9F,KAAA2C,WACAqD,OAAAhG,KAAAgG,OACAE,WAAAlG,KAAAkG,WACAnG,QAAA,GAAAD,GAAAE,KAAAD,SACAgF,IAAA/E,KAAA+E,OAIAe,EAAA3E,MAAA,WACA,GAAAiG,GAAA,GAAAtB,GAAA,MAAuCE,OAAA,EAAAE,WAAA,IAEvC,OADAkB,GAAAtD,KAAA,QACAsD,EAGA,IAAAC,IAAA,oBAEAvB,GAAAwB,SAAA,SAAAvC,EAAAiB,GACA,QAAAqB,EAAA1C,QAAAqB,GACA,SAAAuB,YAAA,sBAGA,WAAAzB,GAAA,MAA+BE,SAAAjG,SAA0ByH,SAAAzC,MAGzDvG,EAAAsB,UACAtB,EAAAoG,UACApG,EAAAsH,WAEAtH,EAAAC,MAAA,SAAAoG,EAAA4C,GACA,UAAA7I,GAAA,SAAAmC,EAAAH,GACA,GAAA8G,GAAA,GAAA9C,GAAAC,EAAA4C,GACAE,EAAA,GAAAC,eAEAD,GAAA3G,OAAA,WACA,GAAA8D,IACAkB,OAAA2B,EAAA3B,OACAE,WAAAyB,EAAAzB,WACAnG,QAAA0F,EAAAkC,EAAAE,yBAAA,IAEA/C,GAAAC,IAAA,eAAA4C,KAAAG,YAAAhD,EAAA/E,QAAA8D,IAAA,gBACA,IAAAnD,GAAA,YAAAiH,KAAAP,SAAAO,EAAAI,YACAhH,GAAA,GAAA+E,GAAApF,EAAAoE,KAGA6C,EAAAzG,QAAA,WACAN,EAAA,GAAA3B,WAAA,4BAGA0I,EAAAK,UAAA,WACApH,EAAA,GAAA3B,WAAA,4BAGA0I,EAAAM,KAAAP,EAAAnD,OAAAmD,EAAA3C,QAEA,YAAA2C,EAAA1C,cACA2C,EAAAO,oBAGA,gBAAAP,IAAAhI,EAAA0B,OACAsG,EAAAQ,aAAA,QAGAT,EAAA3H,QAAAG,QAAA,SAAAd,EAAAN,GACA6I,EAAAS,iBAAAtJ,EAAAM,KAGAuI,EAAAU,cAAAX,EAAA/E,UAAA,KAAA+E,EAAA/E,cAGAnE,EAAAC,MAAA6J,cACC,mBAAA9J,WAAAwB,QFoB4BwG,KAAKlI,EAASC,EAAoB","file":"0.1e9b80bcbcd32e2a69d7.chunk.js","sourceRoot":""}
--------------------------------------------------------------------------------
/docs/0.1e9b80bcbcd32e2a69d7.chunk.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([0],{612:function(t,e,r){r(613),t.exports=self.fetch.bind(self)},613:function(t,e,r){(function(t){!function(e){"use strict";function r(t){if("string"!=typeof t&&(t=String(t)),/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(t))throw new TypeError("Invalid character in header field name");return t.toLowerCase()}function o(t){return"string"!=typeof t&&(t=String(t)),t}function n(t){var e={next:function(){var e=t.shift();return{done:void 0===e,value:e}}};return w.iterable&&(e[Symbol.iterator]=function(){return e}),e}function i(t){this.map={},t instanceof i?t.forEach(function(t,e){this.append(e,t)},this):Array.isArray(t)?t.forEach(function(t){this.append(t[0],t[1])},this):t&&Object.getOwnPropertyNames(t).forEach(function(e){this.append(e,t[e])},this)}function s(e){if(e.bodyUsed)return t.reject(new TypeError("Already read"));e.bodyUsed=!0}function a(e){return new t(function(t,r){e.onload=function(){t(e.result)},e.onerror=function(){r(e.error)}})}function u(t){var e=new FileReader,r=a(e);return e.readAsArrayBuffer(t),r}function f(t){var e=new FileReader,r=a(e);return e.readAsText(t),r}function h(t){for(var e=new Uint8Array(t),r=new Array(e.length),o=0;o-1?e:t}function l(t,e){e=e||{};var r=e.body;if(t instanceof l){if(t.bodyUsed)throw new TypeError("Already read");this.url=t.url,this.credentials=t.credentials,e.headers||(this.headers=new i(t.headers)),this.method=t.method,this.mode=t.mode,r||null==t._bodyInit||(r=t._bodyInit,t.bodyUsed=!0)}else this.url=String(t);if(this.credentials=e.credentials||this.credentials||"omit",!e.headers&&this.headers||(this.headers=new i(e.headers)),this.method=c(e.method||this.method||"GET"),this.mode=e.mode||this.mode||null,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&r)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(r)}function p(t){var e=new FormData;return t.trim().split("&").forEach(function(t){if(t){var r=t.split("="),o=r.shift().replace(/\+/g," "),n=r.join("=").replace(/\+/g," ");e.append(decodeURIComponent(o),decodeURIComponent(n))}}),e}function b(t){var e=new i;return t.split(/\r?\n/).forEach(function(t){var r=t.split(":"),o=r.shift().trim();if(o){var n=r.join(":").trim();e.append(o,n)}}),e}function m(t,e){e||(e={}),this.type="default",this.status="status"in e?e.status:200,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in e?e.statusText:"OK",this.headers=new i(e.headers),this.url=e.url||"",this._initBody(t)}if(!e.fetch){var w={searchParams:"URLSearchParams"in e,iterable:"Symbol"in e&&"iterator"in Symbol,blob:"FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(t){return!1}}(),formData:"FormData"in e,arrayBuffer:"ArrayBuffer"in e};if(w.arrayBuffer)var v=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],B=function(t){return t&&DataView.prototype.isPrototypeOf(t)},_=ArrayBuffer.isView||function(t){return t&&v.indexOf(Object.prototype.toString.call(t))>-1};i.prototype.append=function(t,e){t=r(t),e=o(e);var n=this.map[t];this.map[t]=n?n+","+e:e},i.prototype.delete=function(t){delete this.map[r(t)]},i.prototype.get=function(t){return t=r(t),this.has(t)?this.map[t]:null},i.prototype.has=function(t){return this.map.hasOwnProperty(r(t))},i.prototype.set=function(t,e){this.map[r(t)]=o(e)},i.prototype.forEach=function(t,e){for(var r in this.map)this.map.hasOwnProperty(r)&&t.call(e,this.map[r],r,this)},i.prototype.keys=function(){var t=[];return this.forEach(function(e,r){t.push(r)}),n(t)},i.prototype.values=function(){var t=[];return this.forEach(function(e){t.push(e)}),n(t)},i.prototype.entries=function(){var t=[];return this.forEach(function(e,r){t.push([r,e])}),n(t)},w.iterable&&(i.prototype[Symbol.iterator]=i.prototype.entries);var A=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];l.prototype.clone=function(){return new l(this,{body:this._bodyInit})},y.call(l.prototype),y.call(m.prototype),m.prototype.clone=function(){return new m(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new i(this.headers),url:this.url})},m.error=function(){var t=new m(null,{status:0,statusText:""});return t.type="error",t};var T=[301,302,303,307,308];m.redirect=function(t,e){if(-1===T.indexOf(e))throw new RangeError("Invalid status code");return new m(null,{status:e,headers:{location:t}})},e.Headers=i,e.Request=l,e.Response=m,e.fetch=function(e,r){return new t(function(t,o){var n=new l(e,r),i=new XMLHttpRequest;i.onload=function(){var e={status:i.status,statusText:i.statusText,headers:b(i.getAllResponseHeaders()||"")};e.url="responseURL"in i?i.responseURL:e.headers.get("X-Request-URL");var r="response"in i?i.response:i.responseText;t(new m(r,e))},i.onerror=function(){o(new TypeError("Network request failed"))},i.ontimeout=function(){o(new TypeError("Network request failed"))},i.open(n.method,n.url,!0),"include"===n.credentials&&(i.withCredentials=!0),"responseType"in i&&w.blob&&(i.responseType="blob"),n.headers.forEach(function(t,e){i.setRequestHeader(e,t)}),i.send(void 0===n._bodyInit?null:n._bodyInit)})},e.fetch.polyfill=!0}}("undefined"!=typeof self?self:this)}).call(e,r(2))}});
2 | //# sourceMappingURL=0.1e9b80bcbcd32e2a69d7.bundle.map
--------------------------------------------------------------------------------
/docs/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Single Page Apps for GitHub Pages
6 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/docs/674f50d287a8c48dc19ba404d20fe713.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/docs/674f50d287a8c48dc19ba404d20fe713.eot
--------------------------------------------------------------------------------
/docs/7425dac35a7f1437490f3a09d5139d6d.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */@font-face{font-family:FontAwesome;src:url(/674f50d287a8c48dc19ba404d20fe713.eot);src:url(/674f50d287a8c48dc19ba404d20fe713.eot?#iefix&v=4.7.0) format("embedded-opentype"),url(/af7ae505a9eed503f8b8e6982036873e.woff2) format("woff2"),url(/fee66e712a8a08eef5805a46892932ad.woff) format("woff"),url(/b06871f281fee6b241d60582ae9369b9.ttf) format("truetype"),url(/912ec66d7572ff821749319396470bde.svg#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\F000"}.fa-music:before{content:"\F001"}.fa-search:before{content:"\F002"}.fa-envelope-o:before{content:"\F003"}.fa-heart:before{content:"\F004"}.fa-star:before{content:"\F005"}.fa-star-o:before{content:"\F006"}.fa-user:before{content:"\F007"}.fa-film:before{content:"\F008"}.fa-th-large:before{content:"\F009"}.fa-th:before{content:"\F00A"}.fa-th-list:before{content:"\F00B"}.fa-check:before{content:"\F00C"}.fa-close:before,.fa-remove:before,.fa-times:before{content:"\F00D"}.fa-search-plus:before{content:"\F00E"}.fa-search-minus:before{content:"\F010"}.fa-power-off:before{content:"\F011"}.fa-signal:before{content:"\F012"}.fa-cog:before,.fa-gear:before{content:"\F013"}.fa-trash-o:before{content:"\F014"}.fa-home:before{content:"\F015"}.fa-file-o:before{content:"\F016"}.fa-clock-o:before{content:"\F017"}.fa-road:before{content:"\F018"}.fa-download:before{content:"\F019"}.fa-arrow-circle-o-down:before{content:"\F01A"}.fa-arrow-circle-o-up:before{content:"\F01B"}.fa-inbox:before{content:"\F01C"}.fa-play-circle-o:before{content:"\F01D"}.fa-repeat:before,.fa-rotate-right:before{content:"\F01E"}.fa-refresh:before{content:"\F021"}.fa-list-alt:before{content:"\F022"}.fa-lock:before{content:"\F023"}.fa-flag:before{content:"\F024"}.fa-headphones:before{content:"\F025"}.fa-volume-off:before{content:"\F026"}.fa-volume-down:before{content:"\F027"}.fa-volume-up:before{content:"\F028"}.fa-qrcode:before{content:"\F029"}.fa-barcode:before{content:"\F02A"}.fa-tag:before{content:"\F02B"}.fa-tags:before{content:"\F02C"}.fa-book:before{content:"\F02D"}.fa-bookmark:before{content:"\F02E"}.fa-print:before{content:"\F02F"}.fa-camera:before{content:"\F030"}.fa-font:before{content:"\F031"}.fa-bold:before{content:"\F032"}.fa-italic:before{content:"\F033"}.fa-text-height:before{content:"\F034"}.fa-text-width:before{content:"\F035"}.fa-align-left:before{content:"\F036"}.fa-align-center:before{content:"\F037"}.fa-align-right:before{content:"\F038"}.fa-align-justify:before{content:"\F039"}.fa-list:before{content:"\F03A"}.fa-dedent:before,.fa-outdent:before{content:"\F03B"}.fa-indent:before{content:"\F03C"}.fa-video-camera:before{content:"\F03D"}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:"\F03E"}.fa-pencil:before{content:"\F040"}.fa-map-marker:before{content:"\F041"}.fa-adjust:before{content:"\F042"}.fa-tint:before{content:"\F043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\F044"}.fa-share-square-o:before{content:"\F045"}.fa-check-square-o:before{content:"\F046"}.fa-arrows:before{content:"\F047"}.fa-step-backward:before{content:"\F048"}.fa-fast-backward:before{content:"\F049"}.fa-backward:before{content:"\F04A"}.fa-play:before{content:"\F04B"}.fa-pause:before{content:"\F04C"}.fa-stop:before{content:"\F04D"}.fa-forward:before{content:"\F04E"}.fa-fast-forward:before{content:"\F050"}.fa-step-forward:before{content:"\F051"}.fa-eject:before{content:"\F052"}.fa-chevron-left:before{content:"\F053"}.fa-chevron-right:before{content:"\F054"}.fa-plus-circle:before{content:"\F055"}.fa-minus-circle:before{content:"\F056"}.fa-times-circle:before{content:"\F057"}.fa-check-circle:before{content:"\F058"}.fa-question-circle:before{content:"\F059"}.fa-info-circle:before{content:"\F05A"}.fa-crosshairs:before{content:"\F05B"}.fa-times-circle-o:before{content:"\F05C"}.fa-check-circle-o:before{content:"\F05D"}.fa-ban:before{content:"\F05E"}.fa-arrow-left:before{content:"\F060"}.fa-arrow-right:before{content:"\F061"}.fa-arrow-up:before{content:"\F062"}.fa-arrow-down:before{content:"\F063"}.fa-mail-forward:before,.fa-share:before{content:"\F064"}.fa-expand:before{content:"\F065"}.fa-compress:before{content:"\F066"}.fa-plus:before{content:"\F067"}.fa-minus:before{content:"\F068"}.fa-asterisk:before{content:"\F069"}.fa-exclamation-circle:before{content:"\F06A"}.fa-gift:before{content:"\F06B"}.fa-leaf:before{content:"\F06C"}.fa-fire:before{content:"\F06D"}.fa-eye:before{content:"\F06E"}.fa-eye-slash:before{content:"\F070"}.fa-exclamation-triangle:before,.fa-warning:before{content:"\F071"}.fa-plane:before{content:"\F072"}.fa-calendar:before{content:"\F073"}.fa-random:before{content:"\F074"}.fa-comment:before{content:"\F075"}.fa-magnet:before{content:"\F076"}.fa-chevron-up:before{content:"\F077"}.fa-chevron-down:before{content:"\F078"}.fa-retweet:before{content:"\F079"}.fa-shopping-cart:before{content:"\F07A"}.fa-folder:before{content:"\F07B"}.fa-folder-open:before{content:"\F07C"}.fa-arrows-v:before{content:"\F07D"}.fa-arrows-h:before{content:"\F07E"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\F080"}.fa-twitter-square:before{content:"\F081"}.fa-facebook-square:before{content:"\F082"}.fa-camera-retro:before{content:"\F083"}.fa-key:before{content:"\F084"}.fa-cogs:before,.fa-gears:before{content:"\F085"}.fa-comments:before{content:"\F086"}.fa-thumbs-o-up:before{content:"\F087"}.fa-thumbs-o-down:before{content:"\F088"}.fa-star-half:before{content:"\F089"}.fa-heart-o:before{content:"\F08A"}.fa-sign-out:before{content:"\F08B"}.fa-linkedin-square:before{content:"\F08C"}.fa-thumb-tack:before{content:"\F08D"}.fa-external-link:before{content:"\F08E"}.fa-sign-in:before{content:"\F090"}.fa-trophy:before{content:"\F091"}.fa-github-square:before{content:"\F092"}.fa-upload:before{content:"\F093"}.fa-lemon-o:before{content:"\F094"}.fa-phone:before{content:"\F095"}.fa-square-o:before{content:"\F096"}.fa-bookmark-o:before{content:"\F097"}.fa-phone-square:before{content:"\F098"}.fa-twitter:before{content:"\F099"}.fa-facebook-f:before,.fa-facebook:before{content:"\F09A"}.fa-github:before{content:"\F09B"}.fa-unlock:before{content:"\F09C"}.fa-credit-card:before{content:"\F09D"}.fa-feed:before,.fa-rss:before{content:"\F09E"}.fa-hdd-o:before{content:"\F0A0"}.fa-bullhorn:before{content:"\F0A1"}.fa-bell:before{content:"\F0F3"}.fa-certificate:before{content:"\F0A3"}.fa-hand-o-right:before{content:"\F0A4"}.fa-hand-o-left:before{content:"\F0A5"}.fa-hand-o-up:before{content:"\F0A6"}.fa-hand-o-down:before{content:"\F0A7"}.fa-arrow-circle-left:before{content:"\F0A8"}.fa-arrow-circle-right:before{content:"\F0A9"}.fa-arrow-circle-up:before{content:"\F0AA"}.fa-arrow-circle-down:before{content:"\F0AB"}.fa-globe:before{content:"\F0AC"}.fa-wrench:before{content:"\F0AD"}.fa-tasks:before{content:"\F0AE"}.fa-filter:before{content:"\F0B0"}.fa-briefcase:before{content:"\F0B1"}.fa-arrows-alt:before{content:"\F0B2"}.fa-group:before,.fa-users:before{content:"\F0C0"}.fa-chain:before,.fa-link:before{content:"\F0C1"}.fa-cloud:before{content:"\F0C2"}.fa-flask:before{content:"\F0C3"}.fa-cut:before,.fa-scissors:before{content:"\F0C4"}.fa-copy:before,.fa-files-o:before{content:"\F0C5"}.fa-paperclip:before{content:"\F0C6"}.fa-floppy-o:before,.fa-save:before{content:"\F0C7"}.fa-square:before{content:"\F0C8"}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:"\F0C9"}.fa-list-ul:before{content:"\F0CA"}.fa-list-ol:before{content:"\F0CB"}.fa-strikethrough:before{content:"\F0CC"}.fa-underline:before{content:"\F0CD"}.fa-table:before{content:"\F0CE"}.fa-magic:before{content:"\F0D0"}.fa-truck:before{content:"\F0D1"}.fa-pinterest:before{content:"\F0D2"}.fa-pinterest-square:before{content:"\F0D3"}.fa-google-plus-square:before{content:"\F0D4"}.fa-google-plus:before{content:"\F0D5"}.fa-money:before{content:"\F0D6"}.fa-caret-down:before{content:"\F0D7"}.fa-caret-up:before{content:"\F0D8"}.fa-caret-left:before{content:"\F0D9"}.fa-caret-right:before{content:"\F0DA"}.fa-columns:before{content:"\F0DB"}.fa-sort:before,.fa-unsorted:before{content:"\F0DC"}.fa-sort-desc:before,.fa-sort-down:before{content:"\F0DD"}.fa-sort-asc:before,.fa-sort-up:before{content:"\F0DE"}.fa-envelope:before{content:"\F0E0"}.fa-linkedin:before{content:"\F0E1"}.fa-rotate-left:before,.fa-undo:before{content:"\F0E2"}.fa-gavel:before,.fa-legal:before{content:"\F0E3"}.fa-dashboard:before,.fa-tachometer:before{content:"\F0E4"}.fa-comment-o:before{content:"\F0E5"}.fa-comments-o:before{content:"\F0E6"}.fa-bolt:before,.fa-flash:before{content:"\F0E7"}.fa-sitemap:before{content:"\F0E8"}.fa-umbrella:before{content:"\F0E9"}.fa-clipboard:before,.fa-paste:before{content:"\F0EA"}.fa-lightbulb-o:before{content:"\F0EB"}.fa-exchange:before{content:"\F0EC"}.fa-cloud-download:before{content:"\F0ED"}.fa-cloud-upload:before{content:"\F0EE"}.fa-user-md:before{content:"\F0F0"}.fa-stethoscope:before{content:"\F0F1"}.fa-suitcase:before{content:"\F0F2"}.fa-bell-o:before{content:"\F0A2"}.fa-coffee:before{content:"\F0F4"}.fa-cutlery:before{content:"\F0F5"}.fa-file-text-o:before{content:"\F0F6"}.fa-building-o:before{content:"\F0F7"}.fa-hospital-o:before{content:"\F0F8"}.fa-ambulance:before{content:"\F0F9"}.fa-medkit:before{content:"\F0FA"}.fa-fighter-jet:before{content:"\F0FB"}.fa-beer:before{content:"\F0FC"}.fa-h-square:before{content:"\F0FD"}.fa-plus-square:before{content:"\F0FE"}.fa-angle-double-left:before{content:"\F100"}.fa-angle-double-right:before{content:"\F101"}.fa-angle-double-up:before{content:"\F102"}.fa-angle-double-down:before{content:"\F103"}.fa-angle-left:before{content:"\F104"}.fa-angle-right:before{content:"\F105"}.fa-angle-up:before{content:"\F106"}.fa-angle-down:before{content:"\F107"}.fa-desktop:before{content:"\F108"}.fa-laptop:before{content:"\F109"}.fa-tablet:before{content:"\F10A"}.fa-mobile-phone:before,.fa-mobile:before{content:"\F10B"}.fa-circle-o:before{content:"\F10C"}.fa-quote-left:before{content:"\F10D"}.fa-quote-right:before{content:"\F10E"}.fa-spinner:before{content:"\F110"}.fa-circle:before{content:"\F111"}.fa-mail-reply:before,.fa-reply:before{content:"\F112"}.fa-github-alt:before{content:"\F113"}.fa-folder-o:before{content:"\F114"}.fa-folder-open-o:before{content:"\F115"}.fa-smile-o:before{content:"\F118"}.fa-frown-o:before{content:"\F119"}.fa-meh-o:before{content:"\F11A"}.fa-gamepad:before{content:"\F11B"}.fa-keyboard-o:before{content:"\F11C"}.fa-flag-o:before{content:"\F11D"}.fa-flag-checkered:before{content:"\F11E"}.fa-terminal:before{content:"\F120"}.fa-code:before{content:"\F121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\F122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\F123"}.fa-location-arrow:before{content:"\F124"}.fa-crop:before{content:"\F125"}.fa-code-fork:before{content:"\F126"}.fa-chain-broken:before,.fa-unlink:before{content:"\F127"}.fa-question:before{content:"\F128"}.fa-info:before{content:"\F129"}.fa-exclamation:before{content:"\F12A"}.fa-superscript:before{content:"\F12B"}.fa-subscript:before{content:"\F12C"}.fa-eraser:before{content:"\F12D"}.fa-puzzle-piece:before{content:"\F12E"}.fa-microphone:before{content:"\F130"}.fa-microphone-slash:before{content:"\F131"}.fa-shield:before{content:"\F132"}.fa-calendar-o:before{content:"\F133"}.fa-fire-extinguisher:before{content:"\F134"}.fa-rocket:before{content:"\F135"}.fa-maxcdn:before{content:"\F136"}.fa-chevron-circle-left:before{content:"\F137"}.fa-chevron-circle-right:before{content:"\F138"}.fa-chevron-circle-up:before{content:"\F139"}.fa-chevron-circle-down:before{content:"\F13A"}.fa-html5:before{content:"\F13B"}.fa-css3:before{content:"\F13C"}.fa-anchor:before{content:"\F13D"}.fa-unlock-alt:before{content:"\F13E"}.fa-bullseye:before{content:"\F140"}.fa-ellipsis-h:before{content:"\F141"}.fa-ellipsis-v:before{content:"\F142"}.fa-rss-square:before{content:"\F143"}.fa-play-circle:before{content:"\F144"}.fa-ticket:before{content:"\F145"}.fa-minus-square:before{content:"\F146"}.fa-minus-square-o:before{content:"\F147"}.fa-level-up:before{content:"\F148"}.fa-level-down:before{content:"\F149"}.fa-check-square:before{content:"\F14A"}.fa-pencil-square:before{content:"\F14B"}.fa-external-link-square:before{content:"\F14C"}.fa-share-square:before{content:"\F14D"}.fa-compass:before{content:"\F14E"}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:"\F150"}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:"\F151"}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:"\F152"}.fa-eur:before,.fa-euro:before{content:"\F153"}.fa-gbp:before{content:"\F154"}.fa-dollar:before,.fa-usd:before{content:"\F155"}.fa-inr:before,.fa-rupee:before{content:"\F156"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:"\F157"}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:"\F158"}.fa-krw:before,.fa-won:before{content:"\F159"}.fa-bitcoin:before,.fa-btc:before{content:"\F15A"}.fa-file:before{content:"\F15B"}.fa-file-text:before{content:"\F15C"}.fa-sort-alpha-asc:before{content:"\F15D"}.fa-sort-alpha-desc:before{content:"\F15E"}.fa-sort-amount-asc:before{content:"\F160"}.fa-sort-amount-desc:before{content:"\F161"}.fa-sort-numeric-asc:before{content:"\F162"}.fa-sort-numeric-desc:before{content:"\F163"}.fa-thumbs-up:before{content:"\F164"}.fa-thumbs-down:before{content:"\F165"}.fa-youtube-square:before{content:"\F166"}.fa-youtube:before{content:"\F167"}.fa-xing:before{content:"\F168"}.fa-xing-square:before{content:"\F169"}.fa-youtube-play:before{content:"\F16A"}.fa-dropbox:before{content:"\F16B"}.fa-stack-overflow:before{content:"\F16C"}.fa-instagram:before{content:"\F16D"}.fa-flickr:before{content:"\F16E"}.fa-adn:before{content:"\F170"}.fa-bitbucket:before{content:"\F171"}.fa-bitbucket-square:before{content:"\F172"}.fa-tumblr:before{content:"\F173"}.fa-tumblr-square:before{content:"\F174"}.fa-long-arrow-down:before{content:"\F175"}.fa-long-arrow-up:before{content:"\F176"}.fa-long-arrow-left:before{content:"\F177"}.fa-long-arrow-right:before{content:"\F178"}.fa-apple:before{content:"\F179"}.fa-windows:before{content:"\F17A"}.fa-android:before{content:"\F17B"}.fa-linux:before{content:"\F17C"}.fa-dribbble:before{content:"\F17D"}.fa-skype:before{content:"\F17E"}.fa-foursquare:before{content:"\F180"}.fa-trello:before{content:"\F181"}.fa-female:before{content:"\F182"}.fa-male:before{content:"\F183"}.fa-gittip:before,.fa-gratipay:before{content:"\F184"}.fa-sun-o:before{content:"\F185"}.fa-moon-o:before{content:"\F186"}.fa-archive:before{content:"\F187"}.fa-bug:before{content:"\F188"}.fa-vk:before{content:"\F189"}.fa-weibo:before{content:"\F18A"}.fa-renren:before{content:"\F18B"}.fa-pagelines:before{content:"\F18C"}.fa-stack-exchange:before{content:"\F18D"}.fa-arrow-circle-o-right:before{content:"\F18E"}.fa-arrow-circle-o-left:before{content:"\F190"}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:"\F191"}.fa-dot-circle-o:before{content:"\F192"}.fa-wheelchair:before{content:"\F193"}.fa-vimeo-square:before{content:"\F194"}.fa-try:before,.fa-turkish-lira:before{content:"\F195"}.fa-plus-square-o:before{content:"\F196"}.fa-space-shuttle:before{content:"\F197"}.fa-slack:before{content:"\F198"}.fa-envelope-square:before{content:"\F199"}.fa-wordpress:before{content:"\F19A"}.fa-openid:before{content:"\F19B"}.fa-bank:before,.fa-institution:before,.fa-university:before{content:"\F19C"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\F19D"}.fa-yahoo:before{content:"\F19E"}.fa-google:before{content:"\F1A0"}.fa-reddit:before{content:"\F1A1"}.fa-reddit-square:before{content:"\F1A2"}.fa-stumbleupon-circle:before{content:"\F1A3"}.fa-stumbleupon:before{content:"\F1A4"}.fa-delicious:before{content:"\F1A5"}.fa-digg:before{content:"\F1A6"}.fa-pied-piper-pp:before{content:"\F1A7"}.fa-pied-piper-alt:before{content:"\F1A8"}.fa-drupal:before{content:"\F1A9"}.fa-joomla:before{content:"\F1AA"}.fa-language:before{content:"\F1AB"}.fa-fax:before{content:"\F1AC"}.fa-building:before{content:"\F1AD"}.fa-child:before{content:"\F1AE"}.fa-paw:before{content:"\F1B0"}.fa-spoon:before{content:"\F1B1"}.fa-cube:before{content:"\F1B2"}.fa-cubes:before{content:"\F1B3"}.fa-behance:before{content:"\F1B4"}.fa-behance-square:before{content:"\F1B5"}.fa-steam:before{content:"\F1B6"}.fa-steam-square:before{content:"\F1B7"}.fa-recycle:before{content:"\F1B8"}.fa-automobile:before,.fa-car:before{content:"\F1B9"}.fa-cab:before,.fa-taxi:before{content:"\F1BA"}.fa-tree:before{content:"\F1BB"}.fa-spotify:before{content:"\F1BC"}.fa-deviantart:before{content:"\F1BD"}.fa-soundcloud:before{content:"\F1BE"}.fa-database:before{content:"\F1C0"}.fa-file-pdf-o:before{content:"\F1C1"}.fa-file-word-o:before{content:"\F1C2"}.fa-file-excel-o:before{content:"\F1C3"}.fa-file-powerpoint-o:before{content:"\F1C4"}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:"\F1C5"}.fa-file-archive-o:before,.fa-file-zip-o:before{content:"\F1C6"}.fa-file-audio-o:before,.fa-file-sound-o:before{content:"\F1C7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\F1C8"}.fa-file-code-o:before{content:"\F1C9"}.fa-vine:before{content:"\F1CA"}.fa-codepen:before{content:"\F1CB"}.fa-jsfiddle:before{content:"\F1CC"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:"\F1CD"}.fa-circle-o-notch:before{content:"\F1CE"}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:"\F1D0"}.fa-empire:before,.fa-ge:before{content:"\F1D1"}.fa-git-square:before{content:"\F1D2"}.fa-git:before{content:"\F1D3"}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:"\F1D4"}.fa-tencent-weibo:before{content:"\F1D5"}.fa-qq:before{content:"\F1D6"}.fa-wechat:before,.fa-weixin:before{content:"\F1D7"}.fa-paper-plane:before,.fa-send:before{content:"\F1D8"}.fa-paper-plane-o:before,.fa-send-o:before{content:"\F1D9"}.fa-history:before{content:"\F1DA"}.fa-circle-thin:before{content:"\F1DB"}.fa-header:before{content:"\F1DC"}.fa-paragraph:before{content:"\F1DD"}.fa-sliders:before{content:"\F1DE"}.fa-share-alt:before{content:"\F1E0"}.fa-share-alt-square:before{content:"\F1E1"}.fa-bomb:before{content:"\F1E2"}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:"\F1E3"}.fa-tty:before{content:"\F1E4"}.fa-binoculars:before{content:"\F1E5"}.fa-plug:before{content:"\F1E6"}.fa-slideshare:before{content:"\F1E7"}.fa-twitch:before{content:"\F1E8"}.fa-yelp:before{content:"\F1E9"}.fa-newspaper-o:before{content:"\F1EA"}.fa-wifi:before{content:"\F1EB"}.fa-calculator:before{content:"\F1EC"}.fa-paypal:before{content:"\F1ED"}.fa-google-wallet:before{content:"\F1EE"}.fa-cc-visa:before{content:"\F1F0"}.fa-cc-mastercard:before{content:"\F1F1"}.fa-cc-discover:before{content:"\F1F2"}.fa-cc-amex:before{content:"\F1F3"}.fa-cc-paypal:before{content:"\F1F4"}.fa-cc-stripe:before{content:"\F1F5"}.fa-bell-slash:before{content:"\F1F6"}.fa-bell-slash-o:before{content:"\F1F7"}.fa-trash:before{content:"\F1F8"}.fa-copyright:before{content:"\F1F9"}.fa-at:before{content:"\F1FA"}.fa-eyedropper:before{content:"\F1FB"}.fa-paint-brush:before{content:"\F1FC"}.fa-birthday-cake:before{content:"\F1FD"}.fa-area-chart:before{content:"\F1FE"}.fa-pie-chart:before{content:"\F200"}.fa-line-chart:before{content:"\F201"}.fa-lastfm:before{content:"\F202"}.fa-lastfm-square:before{content:"\F203"}.fa-toggle-off:before{content:"\F204"}.fa-toggle-on:before{content:"\F205"}.fa-bicycle:before{content:"\F206"}.fa-bus:before{content:"\F207"}.fa-ioxhost:before{content:"\F208"}.fa-angellist:before{content:"\F209"}.fa-cc:before{content:"\F20A"}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:"\F20B"}.fa-meanpath:before{content:"\F20C"}.fa-buysellads:before{content:"\F20D"}.fa-connectdevelop:before{content:"\F20E"}.fa-dashcube:before{content:"\F210"}.fa-forumbee:before{content:"\F211"}.fa-leanpub:before{content:"\F212"}.fa-sellsy:before{content:"\F213"}.fa-shirtsinbulk:before{content:"\F214"}.fa-simplybuilt:before{content:"\F215"}.fa-skyatlas:before{content:"\F216"}.fa-cart-plus:before{content:"\F217"}.fa-cart-arrow-down:before{content:"\F218"}.fa-diamond:before{content:"\F219"}.fa-ship:before{content:"\F21A"}.fa-user-secret:before{content:"\F21B"}.fa-motorcycle:before{content:"\F21C"}.fa-street-view:before{content:"\F21D"}.fa-heartbeat:before{content:"\F21E"}.fa-venus:before{content:"\F221"}.fa-mars:before{content:"\F222"}.fa-mercury:before{content:"\F223"}.fa-intersex:before,.fa-transgender:before{content:"\F224"}.fa-transgender-alt:before{content:"\F225"}.fa-venus-double:before{content:"\F226"}.fa-mars-double:before{content:"\F227"}.fa-venus-mars:before{content:"\F228"}.fa-mars-stroke:before{content:"\F229"}.fa-mars-stroke-v:before{content:"\F22A"}.fa-mars-stroke-h:before{content:"\F22B"}.fa-neuter:before{content:"\F22C"}.fa-genderless:before{content:"\F22D"}.fa-facebook-official:before{content:"\F230"}.fa-pinterest-p:before{content:"\F231"}.fa-whatsapp:before{content:"\F232"}.fa-server:before{content:"\F233"}.fa-user-plus:before{content:"\F234"}.fa-user-times:before{content:"\F235"}.fa-bed:before,.fa-hotel:before{content:"\F236"}.fa-viacoin:before{content:"\F237"}.fa-train:before{content:"\F238"}.fa-subway:before{content:"\F239"}.fa-medium:before{content:"\F23A"}.fa-y-combinator:before,.fa-yc:before{content:"\F23B"}.fa-optin-monster:before{content:"\F23C"}.fa-opencart:before{content:"\F23D"}.fa-expeditedssl:before{content:"\F23E"}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:"\F240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\F241"}.fa-battery-2:before,.fa-battery-half:before{content:"\F242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\F243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\F244"}.fa-mouse-pointer:before{content:"\F245"}.fa-i-cursor:before{content:"\F246"}.fa-object-group:before{content:"\F247"}.fa-object-ungroup:before{content:"\F248"}.fa-sticky-note:before{content:"\F249"}.fa-sticky-note-o:before{content:"\F24A"}.fa-cc-jcb:before{content:"\F24B"}.fa-cc-diners-club:before{content:"\F24C"}.fa-clone:before{content:"\F24D"}.fa-balance-scale:before{content:"\F24E"}.fa-hourglass-o:before{content:"\F250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\F251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\F252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\F253"}.fa-hourglass:before{content:"\F254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\F255"}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:"\F256"}.fa-hand-scissors-o:before{content:"\F257"}.fa-hand-lizard-o:before{content:"\F258"}.fa-hand-spock-o:before{content:"\F259"}.fa-hand-pointer-o:before{content:"\F25A"}.fa-hand-peace-o:before{content:"\F25B"}.fa-trademark:before{content:"\F25C"}.fa-registered:before{content:"\F25D"}.fa-creative-commons:before{content:"\F25E"}.fa-gg:before{content:"\F260"}.fa-gg-circle:before{content:"\F261"}.fa-tripadvisor:before{content:"\F262"}.fa-odnoklassniki:before{content:"\F263"}.fa-odnoklassniki-square:before{content:"\F264"}.fa-get-pocket:before{content:"\F265"}.fa-wikipedia-w:before{content:"\F266"}.fa-safari:before{content:"\F267"}.fa-chrome:before{content:"\F268"}.fa-firefox:before{content:"\F269"}.fa-opera:before{content:"\F26A"}.fa-internet-explorer:before{content:"\F26B"}.fa-television:before,.fa-tv:before{content:"\F26C"}.fa-contao:before{content:"\F26D"}.fa-500px:before{content:"\F26E"}.fa-amazon:before{content:"\F270"}.fa-calendar-plus-o:before{content:"\F271"}.fa-calendar-minus-o:before{content:"\F272"}.fa-calendar-times-o:before{content:"\F273"}.fa-calendar-check-o:before{content:"\F274"}.fa-industry:before{content:"\F275"}.fa-map-pin:before{content:"\F276"}.fa-map-signs:before{content:"\F277"}.fa-map-o:before{content:"\F278"}.fa-map:before{content:"\F279"}.fa-commenting:before{content:"\F27A"}.fa-commenting-o:before{content:"\F27B"}.fa-houzz:before{content:"\F27C"}.fa-vimeo:before{content:"\F27D"}.fa-black-tie:before{content:"\F27E"}.fa-fonticons:before{content:"\F280"}.fa-reddit-alien:before{content:"\F281"}.fa-edge:before{content:"\F282"}.fa-credit-card-alt:before{content:"\F283"}.fa-codiepie:before{content:"\F284"}.fa-modx:before{content:"\F285"}.fa-fort-awesome:before{content:"\F286"}.fa-usb:before{content:"\F287"}.fa-product-hunt:before{content:"\F288"}.fa-mixcloud:before{content:"\F289"}.fa-scribd:before{content:"\F28A"}.fa-pause-circle:before{content:"\F28B"}.fa-pause-circle-o:before{content:"\F28C"}.fa-stop-circle:before{content:"\F28D"}.fa-stop-circle-o:before{content:"\F28E"}.fa-shopping-bag:before{content:"\F290"}.fa-shopping-basket:before{content:"\F291"}.fa-hashtag:before{content:"\F292"}.fa-bluetooth:before{content:"\F293"}.fa-bluetooth-b:before{content:"\F294"}.fa-percent:before{content:"\F295"}.fa-gitlab:before{content:"\F296"}.fa-wpbeginner:before{content:"\F297"}.fa-wpforms:before{content:"\F298"}.fa-envira:before{content:"\F299"}.fa-universal-access:before{content:"\F29A"}.fa-wheelchair-alt:before{content:"\F29B"}.fa-question-circle-o:before{content:"\F29C"}.fa-blind:before{content:"\F29D"}.fa-audio-description:before{content:"\F29E"}.fa-volume-control-phone:before{content:"\F2A0"}.fa-braille:before{content:"\F2A1"}.fa-assistive-listening-systems:before{content:"\F2A2"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:"\F2A3"}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:"\F2A4"}.fa-glide:before{content:"\F2A5"}.fa-glide-g:before{content:"\F2A6"}.fa-sign-language:before,.fa-signing:before{content:"\F2A7"}.fa-low-vision:before{content:"\F2A8"}.fa-viadeo:before{content:"\F2A9"}.fa-viadeo-square:before{content:"\F2AA"}.fa-snapchat:before{content:"\F2AB"}.fa-snapchat-ghost:before{content:"\F2AC"}.fa-snapchat-square:before{content:"\F2AD"}.fa-pied-piper:before{content:"\F2AE"}.fa-first-order:before{content:"\F2B0"}.fa-yoast:before{content:"\F2B1"}.fa-themeisle:before{content:"\F2B2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\F2B3"}.fa-fa:before,.fa-font-awesome:before{content:"\F2B4"}.fa-handshake-o:before{content:"\F2B5"}.fa-envelope-open:before{content:"\F2B6"}.fa-envelope-open-o:before{content:"\F2B7"}.fa-linode:before{content:"\F2B8"}.fa-address-book:before{content:"\F2B9"}.fa-address-book-o:before{content:"\F2BA"}.fa-address-card:before,.fa-vcard:before{content:"\F2BB"}.fa-address-card-o:before,.fa-vcard-o:before{content:"\F2BC"}.fa-user-circle:before{content:"\F2BD"}.fa-user-circle-o:before{content:"\F2BE"}.fa-user-o:before{content:"\F2C0"}.fa-id-badge:before{content:"\F2C1"}.fa-drivers-license:before,.fa-id-card:before{content:"\F2C2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\F2C3"}.fa-quora:before{content:"\F2C4"}.fa-free-code-camp:before{content:"\F2C5"}.fa-telegram:before{content:"\F2C6"}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:"\F2C7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\F2C8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\F2C9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\F2CA"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\F2CB"}.fa-shower:before{content:"\F2CC"}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:"\F2CD"}.fa-podcast:before{content:"\F2CE"}.fa-window-maximize:before{content:"\F2D0"}.fa-window-minimize:before{content:"\F2D1"}.fa-window-restore:before{content:"\F2D2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\F2D3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\F2D4"}.fa-bandcamp:before{content:"\F2D5"}.fa-grav:before{content:"\F2D6"}.fa-etsy:before{content:"\F2D7"}.fa-imdb:before{content:"\F2D8"}.fa-ravelry:before{content:"\F2D9"}.fa-eercast:before{content:"\F2DA"}.fa-microchip:before{content:"\F2DB"}.fa-snowflake-o:before{content:"\F2DC"}.fa-superpowers:before{content:"\F2DD"}.fa-wpexplorer:before{content:"\F2DE"}.fa-meetup:before{content:"\F2E0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}
5 | /*# sourceMappingURL=app.2af45f166b6c8f44277a.bundle.map*/
--------------------------------------------------------------------------------
/docs/af7ae505a9eed503f8b8e6982036873e.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/docs/af7ae505a9eed503f8b8e6982036873e.woff2
--------------------------------------------------------------------------------
/docs/app.2af45f166b6c8f44277a.bundle.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"7425dac35a7f1437490f3a09d5139d6d.css","sourceRoot":""}
--------------------------------------------------------------------------------
/docs/app.86ebb9011c2802314f3c.bundle.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"7425dac35a7f1437490f3a09d5139d6d.css","sourceRoot":""}
--------------------------------------------------------------------------------
/docs/b06871f281fee6b241d60582ae9369b9.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/docs/b06871f281fee6b241d60582ae9369b9.ttf
--------------------------------------------------------------------------------
/docs/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/docs/favicon.ico
--------------------------------------------------------------------------------
/docs/fee66e712a8a08eef5805a46892932ad.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/docs/fee66e712a8a08eef5805a46892932ad.woff
--------------------------------------------------------------------------------
/docs/images/aurelia---screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/docs/images/aurelia---screen.png
--------------------------------------------------------------------------------
/docs/images/aurelia-icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/docs/images/aurelia-icon-512x512.png
--------------------------------------------------------------------------------
/docs/images/avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/docs/images/avatar.jpg
--------------------------------------------------------------------------------
/docs/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/docs/images/favicon.ico
--------------------------------------------------------------------------------
/docs/images/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/docs/images/hero.png
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 | Web Renaissance
--------------------------------------------------------------------------------
/docs/vendor.23fd87a635a88ae059ed.bundle.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([2],{611:function(n,o,p){n.exports=p(2)}},[611]);
2 | //# sourceMappingURL=vendor.23fd87a635a88ae059ed.bundle.map
--------------------------------------------------------------------------------
/docs/vendor.23fd87a635a88ae059ed.bundle.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["webpack:///vendor.23fd87a635a88ae059ed.bundle.js"],"names":["webpackJsonp","611","module","exports","__webpack_require__"],"mappings":"AAAAA,cAAc,IAERC,IACA,SAAUC,EAAQC,EAASC,GAEjCF,EAAOC,QAAUC,EAAoB,MAKlC","file":"vendor.23fd87a635a88ae059ed.bundle.js","sourceRoot":""}
--------------------------------------------------------------------------------
/firebase.rules.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | ".read": true,
4 | ".write": "auth != null",
5 | "content": {
6 | ".indexOn": ["type", "isPublished"]
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | <%- htmlWebpackPlugin.options.metadata.title %>
6 |
7 | <% if (htmlWebpackPlugin.options.metadata.description) { %>
8 |
9 | <% } %>
10 |
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
<%- htmlWebpackPlugin.options.metadata.title %>
23 |
24 |
25 |
26 |
61 |
62 | <% if (htmlWebpackPlugin.options.metadata.server) { %>
63 |
64 |
65 | <% } %>
66 |
71 |
72 |
73 |
--------------------------------------------------------------------------------
/package-scripts.js:
--------------------------------------------------------------------------------
1 | const { series, crossEnv, concurrent, rimraf } = require("nps-utils");
2 |
3 | module.exports = {
4 | scripts: {
5 | default: "nps webpack",
6 | test: {
7 | default: "nps test.jest",
8 | jest: {
9 | default: series(rimraf("test/coverage-jest"), "jest"),
10 | accept: "jest -u",
11 | watch: "jest --watch"
12 | },
13 | lint: {
14 | default: "eslint src",
15 | fix: "eslint --fix"
16 | },
17 | all: concurrent({
18 | jest: "nps test.jest",
19 | lint: "nps test.lint"
20 | })
21 | },
22 | build: "nps webpack.build",
23 | webpack: {
24 | default: "nps webpack.server",
25 | build: {
26 | before: rimraf("dist"),
27 | default: "nps webpack.build.production",
28 | development: {
29 | default: series("nps webpack.build.before", "webpack --progress -d"),
30 | extractCss: series("nps webpack.build.before", "webpack --progress -d --env.extractCss"),
31 | serve: series.nps("webpack.build.development", "serve")
32 | },
33 | production: {
34 | inlineCss: series("nps webpack.build.before", "webpack --progress -p --env.production"),
35 | default: series("nps webpack.build.before", "webpack --progress -p --env.production --env.extractCss"),
36 | serve: series.nps("webpack.build.production", "serve")
37 | }
38 | },
39 | server: {
40 | default: `webpack-dev-server -d --devtool '#source-map' --inline --env.server`,
41 | extractCss: `webpack-dev-server -d --devtool '#source-map' --inline --env.server --env.extractCss`,
42 | hmr: `webpack-dev-server -d --devtool '#source-map' --inline --hot --env.server`
43 | }
44 | },
45 | serve: "http-server dist --cors"
46 | }
47 | };
48 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blog",
3 | "version": "0.3.0",
4 | "description": "A starter kit for a gh-pages Aurelia Blog.",
5 | "repository": "git+ssh://git@github.com/SteveHartzog/blog.git",
6 | "keywords": [
7 | "aurelia",
8 | "gh-pages",
9 | "blog",
10 | "webpack"
11 | ],
12 | "license": "CC0-1.0",
13 | "contributors": [
14 | {
15 | "name": "Steve Hartzog",
16 | "email": "steven.hartzog@gmail.com",
17 | "url": "https://about.me/Steve.Hartzog"
18 | },
19 | {
20 | "name": "Carlos Moran"
21 | },
22 | {
23 | "name": "Dwayne Charrington",
24 | "email": "dwaynecharrington@gmail.com",
25 | "url": "http://ilikekillnerds.com"
26 | }
27 | ],
28 | "bugs": {
29 | "url": "https://github.com/SteveHartzog/blog/issues"
30 | },
31 | "homepage": "https://github.com/SteveHartzog/blog#readme",
32 | "scripts": {
33 | "start": "nps",
34 | "test": "nps test"
35 | },
36 | "jest": {
37 | "modulePaths": [
38 | "/src",
39 | "/node_modules"
40 | ],
41 | "moduleFileExtensions": [
42 | "js",
43 | "json",
44 | "ts"
45 | ],
46 | "transform": {
47 | "^.+\\.(ts|tsx)$": "/node_modules/ts-jest/preprocessor.js"
48 | },
49 | "testRegex": "\\.spec\\.(ts|js)x?$",
50 | "setupFiles": [
51 | "/test/jest-pretest.ts"
52 | ],
53 | "testEnvironment": "node",
54 | "moduleNameMapper": {
55 | "aurelia-(.*)": "/node_modules/$1"
56 | }
57 | },
58 | "dependencies": {
59 | "aurelia-bootstrapper": "^2.2.0",
60 | "aurelia-fetch-client": "1.3.1",
61 | "aurelia-polyfills": "latest",
62 | "bluebird": "^3.5.1",
63 | "bootstrap": "4.0.0",
64 | "firebase": "4.10.1",
65 | "font-awesome": "4.7.0",
66 | "highlight.js": "^9.12.0",
67 | "isomorphic-fetch": "2.2.1",
68 | "jquery": "3.2.1",
69 | "markdown-it": "8.4.1",
70 | "markdown-it-deflist": "2.0.3",
71 | "markdown-it-emoji": "1.4.0",
72 | "reading-time": "^1.1.0",
73 | "tether": "1.4.3",
74 | "twemoji": "2.5.0"
75 | },
76 | "devDependencies": {
77 | "@firebase/app": "^0.1.5",
78 | "@firebase/auth": "^0.3.1",
79 | "@firebase/database": "^0.1.6",
80 | "@firebase/util": "^0.1.5",
81 | "@types/jest": "^22.0.1",
82 | "@types/lodash": "^4.14.104",
83 | "@types/markdown-it": "0.0.4",
84 | "@types/node": "^9.4.6",
85 | "@types/webpack": "^3.0.4",
86 | "aurelia-cli": "^0.32.0",
87 | "aurelia-event-aggregator": "^1.0.1",
88 | "aurelia-loader-nodejs": "^1.0.1",
89 | "aurelia-pal-nodejs": "1.0.0-beta.2.0.0",
90 | "aurelia-testing": "^1.0.0-beta.3.0.1",
91 | "aurelia-tools": "^1.0.0",
92 | "aurelia-webpack-plugin": "2.0.0-rc.4",
93 | "awesome-typescript-loader": "^3.4.1",
94 | "copy-webpack-plugin": "4.3.1",
95 | "cross-env": "5.1.3",
96 | "css-loader": "^0.28.8",
97 | "del": "^3.0.0",
98 | "expose-loader": "^0.7.4",
99 | "extract-text-webpack-plugin": "^3.0.2",
100 | "file-loader": "^1.1.6",
101 | "gulp": "github:gulpjs/gulp#4.0",
102 | "gulp-rename": "^1.2.2",
103 | "gulp-util": "^3.0.8",
104 | "html-loader": "^0.5.4",
105 | "html-webpack-plugin": "2.30.1",
106 | "http-server": "0.10.0",
107 | "istanbul-instrumenter-loader": "^3.0.0",
108 | "jasmine-core": "2.8.0",
109 | "jest": "^22.0.5",
110 | "jest-cli": "^22.0.5",
111 | "json-loader": "^0.5.7",
112 | "minimatch": "^3.0.2",
113 | "node-sass": "^4.7.2",
114 | "nps": "^5.7.1",
115 | "nps-utils": "^1.5.0",
116 | "popper.js": "^1.13.0",
117 | "postcss-loader": "^2.0.10",
118 | "sass-loader": "^6.0.6",
119 | "style-loader": "^0.19.1",
120 | "through2": "^2.0.1",
121 | "ts-jest": "^22.0.1",
122 | "ts-node": "^5.0.0",
123 | "typescript": "^2.7.2",
124 | "uglify-js": "^3.0.19",
125 | "url-loader": "^0.6.2",
126 | "vinyl-fs": "^2.4.3",
127 | "wait-on": "2.0.2",
128 | "webpack": "3.5.5",
129 | "webpack-dev-server": "2.7.1"
130 | },
131 | "main": "dist/app.bundle.js"
132 | }
133 |
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | ${contentAuthor}
7 | ${siteBlurb}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
23 |
24 |
--------------------------------------------------------------------------------
/src/app.ts:
--------------------------------------------------------------------------------
1 | import { Aurelia, autoinject } from 'aurelia-framework';
2 | import { EventAggregator } from 'aurelia-event-aggregator';
3 | import { Router, RouterConfiguration } from 'aurelia-router';
4 | import { PLATFORM } from 'aurelia-pal';
5 |
6 | let ApplicationConfig = require('./config/application.config.json');
7 | // let AuthorConfig = require("./config/author.config.json");
8 |
9 | import { SiteConfigInterface, AuthorInterface } from './common/interfaces';
10 |
11 | @autoinject
12 | export class App {
13 | router: Router;
14 | public navBar;
15 | public contentTitle: string;
16 | public siteTitle: string;
17 | public contentAuthor: string;
18 | public siteBlurb: string;
19 | public config: SiteConfigInterface;
20 | public isCover: boolean;
21 |
22 | constructor(public event: EventAggregator) {
23 | this.siteBlurb = ApplicationConfig.siteBlurb;
24 | this.config = ApplicationConfig as SiteConfigInterface;
25 | this.isCover = true;
26 | let app = this;
27 | this.event.subscribe('setSiteTitle', () => {
28 | app.siteTitle = app.config.siteTitle;
29 | app.contentTitle = '';
30 | });
31 | this.event.subscribe('setContentTitle', (title) => {
32 | app.contentTitle = title;
33 | });
34 | this.event.subscribe('setContentAuthor', (author) => {
35 | app.contentAuthor = author;
36 | });
37 | }
38 |
39 | smoothScrollTo(elementId, duration) {
40 | let cover = document.getElementById('cover');
41 | this.isCover = false;
42 | // cover.style.display = "none";
43 | cover.className += ' slideOut';
44 | }
45 |
46 | configureRouter(config: RouterConfiguration, router: Router) {
47 | config.title = 'Home';
48 | config.options.pushState = true;
49 |
50 | config.map([
51 | { route: [''], name: 'home', moduleId: PLATFORM.moduleName('pages/home'), nav: true, title: 'Home' },
52 | { route: ['blog'], name: 'blog', moduleId: PLATFORM.moduleName('pages/blog'), nav: false, title: 'Blog' },
53 | { route: ['post/:url'], name: 'post', moduleId: PLATFORM.moduleName('pages/post'), nav: false, title: 'Post' },
54 | { route: ['about'], name: 'about', moduleId: PLATFORM.moduleName('pages/about'), nav: true, title: 'About' },
55 | {
56 | route: ['category/:cat'],
57 | name: 'category',
58 | moduleId: PLATFORM.moduleName('pages/category'),
59 | nav: false,
60 | title: 'Category'
61 | }
62 | // { route: "setup", name: "setup", moduleId: PLATFORM.moduleName("setup/setup"), nav: true, title: "Setup" }
63 | ]);
64 |
65 | this.router = router;
66 | }
67 |
68 | getYear() {
69 | return new Date().getFullYear();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/common/functions.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Convert a Firebase snapshot object into
3 | * an array. Mostly for use when iterating Firebase data
4 | * from within a HTML template.
5 | *
6 | */
7 | export const snapshotToArray = (snapshot, type = null) => {
8 | let returnArr = [];
9 |
10 | snapshot.forEach(childSnapshot => {
11 | let item = childSnapshot.val();
12 |
13 | item.id = childSnapshot.key;
14 |
15 | // If we are filtering by type
16 | if (type !== null) {
17 | if (type === item.type) {
18 | returnArr.push(item);
19 | }
20 | } else {
21 | returnArr.push(item);
22 | }
23 | });
24 |
25 | return returnArr;
26 | };
27 |
28 | export const filterByType = (object, type) => object.filter(key => object.type === type);
--------------------------------------------------------------------------------
/src/common/interfaces.ts:
--------------------------------------------------------------------------------
1 | export type ContentType = 'post' | 'page';
2 | export type ContentStatus = 'published' | 'draft' | 'scheduled' | 'pending-review';
3 |
4 | export interface SiteConfigInterface {
5 | siteTitle?: string | null;
6 | siteBlurb?: string | null;
7 | siteName?: string;
8 | siteDescription?: string;
9 | }
10 |
11 | export interface FirebaseConfigInterface {
12 | apiKey?: string;
13 | authDomain?: string;
14 | databaseURL?: string;
15 | projectId?: string;
16 | storageBucket?: string;
17 | messagingSenderId?: string;
18 | }
19 |
20 | export interface ContentInterface {
21 | title: string;
22 | category?: string;
23 | excerpt?: string;
24 | content?: string;
25 | author?: string;
26 | type: ContentType;
27 | isPublished?: boolean;
28 | slug?: string;
29 | created_at?: string;
30 | updated_at?: number;
31 | status: string;
32 | url: string;
33 | }
34 |
35 | export interface AuthorInterface {
36 | name: string;
37 | avatar?: string;
38 | }
39 |
40 | export interface CategoryInterface {
41 | name: string;
42 | description?: string | null;
43 | parentCategory?: string | null;
44 | isDefault?: boolean;
45 | }
46 |
47 | export interface ISocial {
48 | twitter: string;
49 | github: string;
50 | googleplus: string;
51 | linkedin: string;
52 | }
53 |
54 | export interface CommentInterface {
55 | userId?: string;
56 | contentId?: string;
57 | content?: string;
58 | }
59 |
--------------------------------------------------------------------------------
/src/config/application.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "siteTitle": "Web Renaissance",
3 | "siteBlurb": "Dark matter developer enjoying the JavaScript renaissance and TypeScript reformation. :)",
4 | "siteName": "WebRenaissance.org",
5 | "siteDescription": "Dark matter developer enjoying the JavaScript renaissance and TypeScript reformation. :)",
6 | "avatar": "avatar.jpg",
7 | "coverImage": "hero.png",
8 | "url": "https://webrenaissance.org/"
9 | }
10 |
--------------------------------------------------------------------------------
/src/config/author.config.json:
--------------------------------------------------------------------------------
1 | [{
2 | "name": "Steve Hartzog",
3 | "blog": "http://webrenaissance.org/",
4 | "quote": "Dark matter developer enjoying the JavaScript renaissance and TypeScript reformation. :)",
5 | "avatar": "avatar.jpg",
6 | "social": {
7 | "googleplus": "SteveHartzog",
8 | "twitter": "SteveHartzog",
9 | "github": "SteveHartzog",
10 | "linkedin": null
11 | }
12 | }]
--------------------------------------------------------------------------------
/src/config/firebase.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiKey": "AIzaSyBMcxjjAdJAkd7hwQbvmIpAV_LsirDpjlI",
3 | "authDomain": "blog-e668a.firebaseapp.com",
4 | "databaseURL": "https://blog-e668a.firebaseio.com/",
5 | "projectId": "blog-e668a",
6 | "storageBucket": "blog-e668a.appspot.com",
7 | "messagingSenderId": "529163607438"
8 | }
9 |
--------------------------------------------------------------------------------
/src/config/social.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "googleplus": "SteveHartzog",
3 | "twitter": "SteveHartzog",
4 | "github": "SteveHartzog",
5 | "linkedin": null
6 | }
7 |
--------------------------------------------------------------------------------
/src/environment.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | debug: true,
3 | testing: true
4 | };
5 |
--------------------------------------------------------------------------------
/src/images/aurelia---screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/src/images/aurelia---screen.png
--------------------------------------------------------------------------------
/src/images/aurelia-icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/src/images/aurelia-icon-512x512.png
--------------------------------------------------------------------------------
/src/images/avatar.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/src/images/avatar.jpg
--------------------------------------------------------------------------------
/src/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/src/images/favicon.ico
--------------------------------------------------------------------------------
/src/images/hero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SteveHartzog/blog/a84452d488fb9e6cd2ce887f1e89152752b12034/src/images/hero.png
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | ///
2 | // we want font-awesome to load as soon as possible to show the fa-spinner
3 | import "font-awesome/css/font-awesome.min.css";
4 | // import '../node_modules/tether/dist/js/tether.min'
5 | import "./styles/site.scss";
6 | import { Aurelia, PLATFORM } from "aurelia-framework";
7 | import * as Bluebird from "bluebird";
8 | import * as firebase from "firebase";
9 |
10 | // Import Firebase config
11 | let FirebaseConfig = require("./config/firebase.config.json");
12 |
13 | // Store Firebase instance on global window
14 | window.firebase = firebase.initializeApp(FirebaseConfig);
15 |
16 | // remove out if you don't want a Promise polyfill (remove also from webpack.config.js)
17 | Bluebird.config({ warnings: { wForgottenReturn: false } });
18 |
19 | export async function configure(aurelia: Aurelia) {
20 | aurelia.use.standardConfiguration().feature(PLATFORM.moduleName("resources/index"));
21 | // .developmentLogging();
22 |
23 | // Uncomment the line below to enable animation.
24 | // aurelia.use.plugin(/* @import */ 'aurelia-animator-css');
25 | // if the css animator is enabled, add swap-order="after" to all router-view elements
26 |
27 | // Anyone wanting to use HTMLImports to load views, will need to install the following plugin.
28 | // aurelia.use.plugin(/* @import */ 'aurelia-html-import-template-loader')
29 |
30 | await aurelia.start();
31 | await aurelia.setRoot(PLATFORM.moduleName("app"));
32 | }
33 |
--------------------------------------------------------------------------------
/src/pages/about.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
About
5 |
6 |
7 |
8 |
9 |
10 |
11 | About Me
12 |
13 |

14 |
${config.siteBlurb}
15 |
16 |
17 |
18 |
19 | Author of the Team Essentials extension for VS Code.
20 | Speaker @ Nova Code Camp on "Introduction to Aurelia", and @ AngularJS-DC meetup "Aurelia vs. Angular".
21 |
22 |
23 |

24 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/src/pages/about.ts:
--------------------------------------------------------------------------------
1 | import { autoinject } from "aurelia-framework";
2 | let ApplicationConfig = require("../config/application.config.json");
3 | let SocialConfig = require("../config/social.config.json");
4 |
5 | import { SiteConfigInterface } from "../common/interfaces";
6 |
7 | @autoinject
8 | export class About {
9 | private config: SiteConfigInterface = ApplicationConfig as SiteConfigInterface;
10 | private socialConfig = SocialConfig;
11 | }
12 |
--------------------------------------------------------------------------------
/src/pages/blog.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
${post.posted} by
8 | ${post.postedBy}
9 |
10 | ${cat}
11 |
12 |
13 |
${post.description}
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/pages/blog.ts:
--------------------------------------------------------------------------------
1 | import { inject } from 'aurelia-framework';
2 | import { DataService } from '../services/dataService';
3 |
4 | @inject(DataService)
5 | export class Blog{
6 | posts: BlogPost[];
7 |
8 | constructor (private ds) {
9 | }
10 |
11 | async activate(): Promise {
12 | this.posts = await this.ds.loadPosts();
13 | }
14 | }
--------------------------------------------------------------------------------
/src/pages/category.html:
--------------------------------------------------------------------------------
1 |
2 | ${heading}
3 |
11 |
--------------------------------------------------------------------------------
/src/pages/category.ts:
--------------------------------------------------------------------------------
1 | import { inject } from 'aurelia-framework';
2 | import { DataService } from '../services/dataService';
3 | import { Markdown } from '../services/markdown';
4 |
5 | @inject(DataService, Markdown)
6 | export class Category{
7 | heading: string;
8 | posts: BlogPost[];
9 |
10 | constructor (private ds, private md) {
11 | }
12 |
13 | async activate(params): Promise {
14 | this.heading = 'Category: ' + params.cat;
15 | this.posts = await this.ds.getPostsByCategory(params.cat.toLowerCase());
16 | }
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/src/pages/contact.html:
--------------------------------------------------------------------------------
1 |
2 | Contact
3 |
--------------------------------------------------------------------------------
/src/pages/contact.ts:
--------------------------------------------------------------------------------
1 | export class Contact{
2 | }
--------------------------------------------------------------------------------
/src/pages/home.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 | Posted by
10 |
11 |
12 |
${post.postedBy}
13 | on
14 |
15 | .
16 |
17 |
18 | ${post.description}
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/pages/home.ts:
--------------------------------------------------------------------------------
1 | import { autoinject } from "aurelia-framework";
2 | import { EventAggregator } from "aurelia-event-aggregator";
3 | import { DataService } from "../services/dataService";
4 |
5 | import { SiteConfigInterface, AuthorInterface } from "../common/interfaces";
6 |
7 | let ApplicationConfig = require("../config/application.config.json");
8 | let AuthorConfig = require("../config/author.config.json");
9 |
10 | @autoinject
11 | export class Posts {
12 | posts: BlogPost[];
13 | content: any;
14 |
15 | constructor(public event: EventAggregator, private ds: DataService) {
16 | let author = AuthorConfig[0] as AuthorInterface;
17 | this.event.publish("setSiteTitle");
18 | this.event.publish("setContentAuthor", author.name);
19 | }
20 |
21 | // attached() {
22 | // this.event.publish('hideTitlebar', true);
23 | // }
24 |
25 | async activate(): Promise {
26 | this.content = await this.ds.getContent();
27 | this.posts = await this.ds.loadPosts();
28 |
29 | // console.log(this.content);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/pages/portfolio.html:
--------------------------------------------------------------------------------
1 |
2 | Portfolio
3 |
--------------------------------------------------------------------------------
/src/pages/portfolio.ts:
--------------------------------------------------------------------------------
1 | export class Portfolio{
2 | }
--------------------------------------------------------------------------------
/src/pages/post.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ${post.title}
6 | Posted by
7 | ${author.name} on
8 |
9 | .
10 |
11 |
12 |
13 |
14 |
27 |
28 |

29 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/pages/post.ts:
--------------------------------------------------------------------------------
1 | import { autoinject } from "aurelia-framework";
2 | import { EventAggregator } from "aurelia-event-aggregator";
3 | import { DataService } from "../services/dataService";
4 | import { Markdown } from "../services/markdown";
5 | import * as _ from "lodash";
6 |
7 | let AuthorConfig = require("../config/author.config.json");
8 | import { ContentInterface, AuthorInterface, ISocial } from "../common/interfaces";
9 |
10 | @autoinject
11 | export class Post {
12 | public author: AuthorInterface;
13 | public social: ISocial;
14 |
15 | post: ContentInterface;
16 |
17 | constructor(public event: EventAggregator, private ds: DataService, private md: Markdown) {}
18 |
19 | async activate(params): Promise {
20 | this.post = await this.ds.getPostByUrl(params.url);
21 | this.event.publish("setContentTitle", this.post.title);
22 | let authorConfig = JSON.parse(JSON.stringify(AuthorConfig)) as AuthorInterface[];
23 | this.author = _.find(authorConfig, author => {
24 | return author.name === this.post.author;
25 | });
26 | this.post["fullUrl"] = encodeURIComponent(window.location.href.replace("/post/", "/#/post/"));
27 |
28 | // Reset scroll height carried over from home
29 | window.scrollTo(0, 0);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/resources/elements/categories/categories.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
--------------------------------------------------------------------------------
/src/resources/elements/categories/categories.ts:
--------------------------------------------------------------------------------
1 | import { inject } from 'aurelia-framework';
2 | import { DataService } from '../../../services/dataService';
3 |
4 | @inject(DataService)
5 | export class Categories {
6 | categories: string[];
7 |
8 | constructor (private ds) {
9 | }
10 |
11 | async attached(): Promise {
12 | this.categories = await this.ds.loadCategories();
13 | }
14 | }
--------------------------------------------------------------------------------
/src/resources/elements/comments/comments.html:
--------------------------------------------------------------------------------
1 |
2 |
20 |
--------------------------------------------------------------------------------
/src/resources/elements/comments/comments.ts:
--------------------------------------------------------------------------------
1 | import {autoinject, bindable} from 'aurelia-framework';
2 | import * as firebase from 'firebase';
3 | import {CommentInterface} from '../../../common/interfaces';
4 |
5 | import {DataService} from '../../../services/dataService';
6 |
7 | @autoinject
8 | export class CommentsCustomElement {
9 | @bindable contentId;
10 | isLoggedIn: boolean = false;
11 |
12 | private comments: CommentInterface[] = [];
13 |
14 | constructor(private ds: DataService) {
15 | firebase.auth().onAuthStateChanged(user => {
16 | if (user) {
17 | console.log(user);
18 | this.isLoggedIn = true;
19 | } else {
20 | this.isLoggedIn = false;
21 | }
22 | });
23 | }
24 |
25 | login(where: 'facebook' | 'google' | 'twitter') {
26 | switch(where) {
27 | case 'facebook':
28 | let facebookProvider = new firebase.auth.FacebookAuthProvider();
29 |
30 | firebase.auth().signInWithPopup(facebookProvider).catch(() => this.isLoggedIn = false);
31 | break;
32 |
33 | case 'google':
34 | let googleProvider = new firebase.auth.GoogleAuthProvider();
35 |
36 | firebase.auth().signInWithPopup(googleProvider).catch(() => this.isLoggedIn = false);
37 | break;
38 |
39 | case 'twitter':
40 | let twitterProvider = new firebase.auth.TwitterAuthProvider();
41 |
42 | firebase.auth().signInWithPopup(twitterProvider).catch(() => this.isLoggedIn = false);
43 | break;
44 | }
45 | }
46 |
47 | contentIdChanged(newVal) {
48 | if (newVal) {
49 | this.ds.getComments(newVal).then(comments => this.comments = comments);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/resources/elements/nav-bar.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
${contentTitle}
10 |
11 |
12 |
${siteTitle}
13 | ${author}
14 |
15 |
29 |
30 |
--------------------------------------------------------------------------------
/src/resources/elements/profile.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | ${author.name}
4 | ${author.quote}
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/resources/elements/readingTime/readingTime.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/resources/elements/readingTime/readingTime.ts:
--------------------------------------------------------------------------------
1 | import { bindable } from 'aurelia-framework';
2 | import * as readingTime from 'reading-time';
3 | import * as util from '../../../services/utilities';
4 |
5 | export class ReadingTime {
6 | @bindable selector: string;
7 | currentScrollHeight: number;
8 | eta: HTMLElement;
9 | debounceTimout: number = 250;
10 | displayTime: number = 3000;
11 | fadeTime: number = 800;
12 | intervalId;
13 | displayTimeId;
14 | stats;
15 |
16 | attached() {
17 | this.currentScrollHeight = document.querySelector(this.selector)['offsetHeight'];
18 | let postBody = document.querySelector(this.selector).textContent;
19 | let minutesLeft = `${Math.round(readingTime(postBody).minutes)} minutes left`;
20 | this.stats = readingTime(postBody);
21 | this.eta = document.querySelector('.eta');
22 |
23 | clearInterval(this.intervalId);
24 | this.intervalId = this.addEta(minutesLeft);
25 |
26 | let that = this;
27 | window.addEventListener('scroll', this.onScroll(function (event) {
28 | clearInterval(that.intervalId);
29 | let currentScrollPosition = event.target.scrollingElement.scrollTop + 65;
30 | let secondsLeft = Math.round(((that.currentScrollHeight-currentScrollPosition)/that.currentScrollHeight)*(that.stats.time/1000));
31 | let minutesLeft = Math.floor(secondsLeft/60);
32 | if (minutesLeft > 0) {
33 | that.intervalId = that.addEta(minutesLeft !== 0 ? `${minutesLeft} minutes left` : `Thanks for reading`);
34 | } else {
35 | that.intervalId = that.addEta(secondsLeft > 46 ? `< 1 minute left` : `Thanks for reading`);
36 | }
37 | }, this.debounceTimout));
38 | }
39 |
40 | onScroll(func, wait, immediate?) {
41 | var timeout;
42 | return function() {
43 | var context = this, args = arguments;
44 | var later = function() {
45 | timeout = null;
46 | if (!immediate) func.apply(context, args);
47 | };
48 | var callNow = immediate && !timeout;
49 | clearTimeout(timeout);
50 | timeout = setTimeout(later, wait);
51 | if (callNow) func.apply(context, event);
52 | };
53 | }
54 |
55 | addEta(eta: string) {
56 | clearTimeout(this.displayTimeId);
57 | this.intervalId = undefined;
58 | this.eta.textContent = eta;
59 | this.eta.style.opacity = '1';
60 | this.eta.style.display = 'block';
61 | eta !== 'Thanks for reading' ? this.delayHide(this.eta, this.displayTime) : null;
62 | }
63 |
64 | delayHide(el, readingTime) {
65 | let that = this;
66 | this.displayTimeId = setTimeout(() => {
67 | el.style.display = 'none';
68 | that.intervalId = util.fadeOut(el, that.fadeTime);
69 | }, readingTime);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/resources/index.ts:
--------------------------------------------------------------------------------
1 | import {FrameworkConfiguration, PLATFORM } from 'aurelia-framework';
2 |
3 | export function configure(config: FrameworkConfiguration) {
4 | config.globalResources([
5 | PLATFORM.moduleName('./value-converters/date-format'),
6 | PLATFORM.moduleName('./elements/profile.html'),
7 | PLATFORM.moduleName('./elements/comments/comments'),
8 | PLATFORM.moduleName('./elements/readingTime/readingTime')
9 | ]);
10 | }
11 |
--------------------------------------------------------------------------------
/src/resources/value-converters/date-format.ts:
--------------------------------------------------------------------------------
1 | import * as moment from 'moment';
2 |
3 | export class DateFormatValueConverter {
4 | toView(value, format) {
5 | if (value === null) {
6 | return '';
7 | }
8 | return moment(value).format(format);
9 | }
10 | }
--------------------------------------------------------------------------------
/src/services/IBlogPost.ts:
--------------------------------------------------------------------------------
1 | interface BlogPost {
2 | body: string;
3 | categories: string[];
4 | description: string;
5 | posted: string;
6 | postedBy: string;
7 | title: string;
8 | }
--------------------------------------------------------------------------------
/src/services/config.ts:
--------------------------------------------------------------------------------
1 | export class Config {
2 | public name: string = "Steve Hartzog";
3 | public authorQuote: string = "Dark matter developer enjoying the JavaScript renaissance and TypeScript reformation. :)";
4 | public url: string = "https://SteveHartzog.github.io/";
5 | public source: string = "https://blog-e668a.firebaseio.com/";
6 | public avatarImage: string = "avatar.jpg";
7 | public coverImage: string = "hero.png";
8 | public googlePlus: string = "SteveHartzog";
9 | public twitter: string = "SteveHartzog";
10 | public github: string = "SteveHartzog";
11 | }
12 |
--------------------------------------------------------------------------------
/src/services/dataService.ts:
--------------------------------------------------------------------------------
1 | import { inject } from 'aurelia-framework';
2 | import * as _ from 'lodash/lodash.min';
3 | import { HttpClient } from 'aurelia-fetch-client';
4 | import * as moment from 'moment';
5 | import * as firebase from 'firebase';
6 |
7 | // Works in TS 2.3.2
8 | // import * as FirebaseConfig from '../config/firebase.config.json';
9 | // Required by TS 2.6.2
10 | let FirebaseConfig = require('../config/firebase.config.json');
11 |
12 | import { CategoryInterface, ContentType, ContentInterface, FirebaseConfigInterface } from '../common/interfaces';
13 | import { snapshotToArray } from '../common/functions';
14 |
15 | // polyfill fetch client conditionally
16 | const fetch = !self.fetch ? System.import('isomorphic-fetch') : Promise.resolve(self.fetch);
17 |
18 | @inject(HttpClient)
19 | export class DataService {
20 | private config: FirebaseConfigInterface = FirebaseConfig;
21 |
22 | posts: {}[];
23 | categories: string[];
24 |
25 | constructor(private http: HttpClient) {
26 | this.http.configure((config) => {
27 | config.useStandardConfiguration().withBaseUrl(this.config.databaseURL);
28 | });
29 | }
30 |
31 | async getContent(type: ContentType = 'post', onlyPublished: boolean = true): Promise {
32 | let data: ContentInterface[] = [];
33 |
34 | if (onlyPublished) {
35 | await firebase
36 | .database()
37 | .ref()
38 | .child('content')
39 | .orderByChild('isPublished')
40 | .equalTo(true)
41 | .once('value', (snapshot) => {
42 | data = snapshotToArray(snapshot, 'post');
43 | });
44 | } else {
45 | await firebase
46 | .database()
47 | .ref()
48 | .child('content')
49 | .orderByChild('type')
50 | .equalTo(type)
51 | .once('value', (snapshot) => (data = snapshotToArray(snapshot)));
52 | }
53 |
54 | return data.sort((a, b) => {
55 | if (a.created_at < b.created_at) {
56 | return -1;
57 | }
58 | if (a.created_at > b.created_at) {
59 | return 1;
60 | }
61 | return 0;
62 | });
63 | }
64 |
65 | async getCategories(): Promise {
66 | let categories: CategoryInterface[];
67 |
68 | await firebase
69 | .database()
70 | .ref()
71 | .child('categories')
72 | .orderByChild('name')
73 | .once('value', (snapshot) => (categories = snapshotToArray(snapshot)));
74 |
75 | return categories;
76 | }
77 |
78 | async getDefaultCategory(): Promise {
79 | let defaultCategory: CategoryInterface = null;
80 |
81 | await firebase
82 | .database()
83 | .ref()
84 | .child('categories')
85 | .orderByChild('isDefault')
86 | .equalTo(true)
87 | .once('value', (snapshot) => (defaultCategory = snapshot.val()));
88 |
89 | return defaultCategory;
90 | }
91 |
92 | async getComments(contentId: string) {
93 | let comments = [];
94 |
95 | await firebase
96 | .database()
97 | .ref()
98 | .child('comments')
99 | .orderByChild('contentId')
100 | .equalTo(contentId)
101 | .once('value', (snapshot) => (comments = snapshotToArray(snapshot)));
102 |
103 | return comments;
104 | }
105 |
106 | async getData(data: string, refresh: boolean = false) {
107 | let items;
108 | try {
109 | const response = await this.http.fetch(data + '.json');
110 | items = await response.json();
111 | } catch (err) {
112 | console.log(`err: ${err}`);
113 | return [];
114 | }
115 | return items;
116 | }
117 |
118 | async loadPosts(refresh: boolean = false): Promise {
119 | if (this.posts === undefined || refresh === true) {
120 | this.posts = _.sortBy(await this.getData('posts'), (post) => {
121 | return moment(post.posted);
122 | }).reverse();
123 | }
124 | return this.posts;
125 | }
126 |
127 | async loadCategories(refresh: boolean = false) {
128 | if (this.categories === undefined || refresh === true) {
129 | this.categories = await this.getData('categories');
130 | }
131 | return this.categories;
132 | }
133 |
134 | async getPostsByCategory(category: string, refresh: boolean = false) {
135 | if (this.posts === undefined || refresh === true) {
136 | this.posts = await this.getData('posts');
137 | }
138 | return _.filter(this.posts, (post) => {
139 | for (let cat of post['categories']) {
140 | if (cat.toLowerCase() === category.toLowerCase()) {
141 | return true;
142 | }
143 | }
144 | return false;
145 | });
146 | }
147 |
148 | async getPostByUrl(url: string, refresh: boolean = false): Promise {
149 | if (this.posts === undefined || refresh === true) {
150 | this.posts = await this.getData('posts');
151 | }
152 | return this.posts[_.findIndex(this.posts, { url: url })];
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/services/markdown.ts:
--------------------------------------------------------------------------------
1 | import * as MarkdownIt from 'markdown-it';
2 | import * as emoji from 'markdown-it-emoji';
3 | import * as twemoji from 'twemoji';
4 | import * as deflist from 'markdown-it-deflist';
5 | import * as hljs from 'highlight.js';
6 |
7 | export class Markdown {
8 | constructor() {
9 | const md = new MarkdownIt({
10 | html: true, // Enable HTML tags in source
11 | xhtmlOut: true, // Use '/' to close single tags (
).
12 | // This is only for full CommonMark compatibility.
13 | breaks: true, // Convert '\n' in paragraphs into
14 | langPrefix: 'language-', // CSS language prefix for fenced blocks. Can be
15 | // useful for external highlighters.
16 | linkify: true, // Autoconvert URL-like text to links
17 |
18 | // Enable some language-neutral replacement + quotes beautification
19 | typographer: true,
20 |
21 | // Double + single quotes replacement pairs, when typographer enabled,
22 | // and smartquotes on. Could be either a String or an Array.
23 | //
24 | // For example, you can use '«»„“' for Russian, '„“‚‘' for German,
25 | // and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp).
26 | quotes: '“”‘’',
27 |
28 | // Highlighter function. Should return escaped HTML,
29 | // or '' if the source string is not changed and should be escaped externaly.
30 | // If result starts with ' +
35 | hljs.highlight(lang, str, true).value +
36 | '
';
37 | } catch (__) {}
38 | }
39 | return '';
40 | }
41 | });
42 | md.use(deflist)
43 | .use(emoji);
44 | md.renderer.rules['emoji'] = function(token, idx) {
45 | return twemoji.parse(token[idx].content);
46 | };
47 | return md;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/services/utilities.ts:
--------------------------------------------------------------------------------
1 | export function extend(...args): {} {
2 | let extended = {};
3 | let deep = false;
4 | let i = 0;
5 | let length = args.length;
6 |
7 | // Check if a deep merge
8 | if (Object.prototype.toString.call( args[0] ) === '[object Boolean]') {
9 | deep = args[0];
10 | i++;
11 | }
12 |
13 | // Merge the object into the extended object
14 | let merge = function (obj) {
15 | for (let prop in obj) {
16 | if (Object.prototype.hasOwnProperty.call( obj, prop )) {
17 | // If deep merge and property is an object, merge properties
18 | if (deep && Object.prototype.toString.call(obj[prop]) === '[object Object]') {
19 | extended[prop] = this.extend( true, extended[prop], obj[prop]);
20 | } else {
21 | extended[prop] = obj[prop];
22 | }
23 | }
24 | }
25 | };
26 |
27 | // Loop through each object and conduct a merge
28 | for ( ; i < length; i++ ) {
29 | let obj = args[i];
30 | merge(obj);
31 | }
32 |
33 | return extended;
34 | }
35 |
36 | export function fadeIn(el, time) {
37 | el.style.opacity = '0';
38 | el.style.display = 'block';
39 | let timer = time || 400;
40 | let timeForSetInterval = 20;
41 | let opacityStart = 0;
42 | let count = timer / timeForSetInterval;
43 | let step = 1 / count;
44 | let i = 0;
45 |
46 | let forClear = setInterval(function(){
47 | let opacity = getComputedStyle(el).opacity;
48 | el.style.opacity = parseFloat(opacity) + parseFloat(step.toString());
49 | i++;
50 | if(i === count){
51 | clearInterval(forClear);
52 | el.style.opacity = 1;
53 | }
54 | }, timeForSetInterval)
55 | }
56 |
57 | export function fadeOut(el, time) {
58 | el.style.display = 'block';
59 | let timer = time || 400;
60 | let timeForSetInterval = 20;
61 | let opacityStart = getComputedStyle(el).opacity;
62 | let count = timer / timeForSetInterval;
63 | let step = parseFloat(opacityStart) / count;
64 | let i = 0;
65 |
66 | let intervalId = setInterval(function(){
67 | let opacity = getComputedStyle(el).opacity;
68 | el.style.opacity = parseFloat(opacity) - parseFloat(step.toString());
69 | i++;
70 | if(i === count){
71 | clearInterval(intervalId);
72 | el.style.opacity = 0;
73 | el.style.display = 'none';
74 | }
75 | }, timeForSetInterval);
76 |
77 | return intervalId;
78 | }
79 |
--------------------------------------------------------------------------------
/src/setup/setup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Setup your Firebase blog
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/setup/setup.ts:
--------------------------------------------------------------------------------
1 | import {ContentInterface, CategoryInterface} from '../common/interfaces';
2 | import * as firebase from 'firebase';
3 |
4 | export class Setup {
5 | private db: any;
6 |
7 | constructor() {
8 | this.db = firebase.database();
9 | }
10 |
11 | populateCategories() {
12 | return new Promise((resolve, reject) => {
13 | firebase.auth().onAuthStateChanged(user => {
14 | if (!user) {
15 | reject();
16 | return;
17 | }
18 |
19 | const categories: CategoryInterface[] = [
20 | {
21 | name: 'Uncategorized',
22 | description: null,
23 | parentCategory: null,
24 | isDefault: true
25 | },
26 | {
27 | name: 'Aurelia',
28 | description: 'Blog posts about the Aurelia Javascript framework',
29 | parentCategory: null,
30 | isDefault: false
31 | }
32 | ];
33 |
34 | categories.forEach(category => {
35 | let newCategoryKey = this.db.ref().child('categories').push().key;
36 | let updates = {};
37 |
38 | updates['/categories/' + newCategoryKey] = category;
39 |
40 | this.db.ref().update(updates);
41 | });
42 | });
43 | });
44 | }
45 |
46 | populatePosts() {
47 | return new Promise((resolve, reject) => {
48 | firebase.auth().onAuthStateChanged(user => {
49 | if (!user) {
50 | reject();
51 | return;
52 | }
53 |
54 | const posts: ContentInterface[] = [
55 | {
56 | title: 'Application Architecture 2015',
57 | category: '-KkpZ2f3uCQGFaycHNDs',
58 | content: 'Here are my thoughts on web application architecture. ```css\nIt was a presentation``` ~~~~\nI did for a company in DC for an interview.\n~~~~ \n \n Google Slides: [Application Architecture](http://slides.com/stevehartzog/apparchitecture#/)',
59 | excerpt: 'JavaScript Application Architecture in 2015.',
60 | author: 'Steve',
61 | type: 'post',
62 | status: 'published',
63 | url: 'app-architecture-2015',
64 | created_at: '2015-01-18 17:15'
65 | },
66 | {
67 | title: 'JavaScript Modules',
68 | category: '-KkpZ2f3uCQGFaycHNDs',
69 | content: 'I put together a quick presentation on the different JavaScript module types. CommonJS, ES2015, AMD. AMD and RequireJS are long overdue for being expunged, deleted and forgotten about. \n \nGoogle Slides: [JavaScript Modules](https://docs.google.com/presentation/d/1qOOLIQWjvzvhjhcRLSNYVSheqK5HOSgH4IJWLI4YS0s/edit?usp=sharing)',
70 | excerpt: 'Another post that describes the current holiday when I was writing it.',
71 | author: 'Steve',
72 | type: 'post',
73 | status: 'published',
74 | url: 'javascript-modules',
75 | created_at: '2015-05-01 17:20'
76 | },
77 | {
78 | title: 'JavaScript Build presentation.',
79 | category: '-KkpZ2f3uCQGFaycHNDs',
80 | content: 'I put together a quick presentation on the different JavaScript build options. \n \nGoogle Slides: (JavaScript Build)[https://docs.google.com/presentation/d/1jCYVxAwmvR9jGM76salvbrEwQV9CdfRlKHKiM5cpXfQ/edit?usp=sharing] \n \n ##tldr; Yep, I like SystemJS. Transpilation. Build pipeline. Kitchen sink. :)',
81 | excerpt: 'JavaScript Build presentation.',
82 | author: 'Steve',
83 | type: 'post',
84 | status: 'published',
85 | url: 'javascript-build',
86 | created_at: '2015-05-22 10:50'
87 | }
88 | ];
89 |
90 | posts.forEach(post => {
91 | let newPostKey = this.db.ref().child('content').push().key;
92 | let updates = {};
93 |
94 | updates['/content/' + newPostKey] = post;
95 |
96 | this.db.ref().update(updates);
97 | });
98 | });
99 | });
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/styles/_aurelia.scss:
--------------------------------------------------------------------------------
1 | $twitter-color: #55acee;
2 | $gplus-color: #dd4b39;
3 | $github-color: #171515;
4 | $linkedin-color: #0e76a8;
5 | $facebook-color: #3b5998;
6 | $eta-border-color: transparent; /* darkgray; */
7 |
8 | body,
9 | html {
10 | font: 400 16px "Open Sans", serif;
11 | height: 100%;
12 | margin: 0;
13 | padding: 0;
14 | color: #3b3b3b;
15 | background: #fff;
16 |
17 | .blogcover .cover .background {
18 | opacity: 1;
19 | display: none;
20 | }
21 | }
22 |
23 | /* Base Layout */
24 | body {
25 | display: grid;
26 | grid-template-columns: 125px 1fr min-content;
27 | grid-template-rows: 55px 1fr min-content;
28 | grid-template-areas:
29 | ". . ."
30 | "left content right"
31 | "footer footer footer";
32 |
33 | nav-bar {
34 | grid-column: 1 / -1;
35 | background: rgba(39, 39, 39, 0.9);
36 | background-image: linear-gradient(rgba(39, 39, 39, 0.9), rgba(56, 56, 56, 0.9));
37 | display: grid;
38 | grid-template-columns: 125px 1fr min-content;
39 | align-content: center;
40 | box-shadow: 0px 1px 10px 0px #a6a6a6;
41 |
42 | > header {
43 | margin-left: 10px;
44 | z-index: 15;
45 |
46 | a:hover {
47 | background: #3287c1;
48 | color: #fff;
49 |
50 | span {
51 | display: inline-block;
52 | width: auto;
53 | }
54 | }
55 |
56 | a {
57 | padding: 9px 12px 6px;
58 | color: #fff;
59 | background: #29292a;
60 |
61 | span {
62 | display: inline-block;
63 | margin-left: 15px;
64 | }
65 |
66 | @media screen and (max-width: 1140px) {
67 | span {
68 | display: none;
69 | }
70 | }
71 | }
72 | }
73 |
74 | .titlebar {
75 | color: #ececf1;
76 | display: flex;
77 | cursor: default;
78 |
79 | h1 {
80 | flex-grow: 1;
81 | }
82 | h1,
83 | h2 {
84 | font: 700 18px "Open Sans", Serif !important;
85 | margin: 0;
86 | padding: 0;
87 | color: #fff;
88 | }
89 | }
90 |
91 | nav.mainnav {
92 | margin-right: 10px;
93 | z-index: 15;
94 |
95 | text-align: right;
96 |
97 | .navwrap:hover .menu,
98 | .navwrap:hover .toggle {
99 | opacity: 1;
100 | }
101 |
102 | .menu {
103 | position: absolute;
104 | width: 270px;
105 | top: 0;
106 | right: 50px;
107 | height: 53px;
108 | padding-top: 17px;
109 | background: rgba(39, 39, 39, 0.9);
110 | background-image: linear-gradient(rgba(39, 39, 39, 0.9), rgba(56, 56, 56, 0.9));
111 | display: inline-block;
112 | margin: 0;
113 | list-style: none;
114 | opacity: 0;
115 | -webkit-transition: opacity 0.4s ease-out;
116 | -moz-transition: opacity 0.4s ease-out;
117 | transition: opacity 0.4s ease-out;
118 |
119 | li {
120 | position: relative;
121 | display: inline-block;
122 | margin: 0;
123 | padding: 0;
124 | }
125 |
126 | li:hover a {
127 | color: #fff;
128 | }
129 |
130 | a {
131 | font: 600 16px "Open Sans", Arial, Sans-Serif;
132 | display: block;
133 | padding: 0 10px;
134 | text-transform: uppercase;
135 | color: #7d7d7d;
136 | }
137 |
138 | a:hover {
139 | color: white;
140 | }
141 | }
142 |
143 | .menu.cover {
144 | background: transparent;
145 |
146 | a {
147 | color: white;
148 | }
149 | }
150 |
151 | .toggle {
152 | display: inline-block;
153 | padding: 0 10px 0 15px;
154 | opacity: 0.6;
155 | -webkit-transition: opacity 0.1s ease-out;
156 | -moz-transition: opacity 0.1s ease-out;
157 |
158 | i {
159 | font-size: 24px;
160 | vertical-align: middle;
161 | color: #b0b0b0;
162 | }
163 | }
164 | }
165 | }
166 |
167 | section.content-left {
168 | grid-area: left;
169 | }
170 | router-view {
171 | grid-area: content;
172 | overflow-y: scroll;
173 | grid-column: 2 / -1;
174 | padding-right: 120px;
175 | padding-top: 30px;
176 |
177 | .posts {
178 | display: grid;
179 | grid-template-columns: repeat(auto-fit, minmax(450px, 1fr));
180 | grid-gap: 50px;
181 |
182 | article {
183 | header {
184 | position: relative;
185 |
186 | h1 {
187 | margin: 30px 0;
188 |
189 | a {
190 | position: relative;
191 | padding: 10px 0;
192 | color: #262b30;
193 | }
194 | a:hover {
195 | color: #007cc3;
196 | }
197 | }
198 |
199 | span {
200 | color: #b6b6b6;
201 | }
202 |
203 | i {
204 | margin: 0 5px;
205 | }
206 |
207 | time {
208 | font-weight: 600;
209 | color: #026ed2;
210 | }
211 |
212 | p {
213 | margin: 30px 0 0;
214 | }
215 | }
216 |
217 | section {
218 | margin: 0 0 30px;
219 | padding-top: 40px;
220 | }
221 |
222 | p {
223 | overflow: hidden;
224 | }
225 | }
226 | }
227 |
228 | .postindex article {
229 | border-bottom: 1px solid #d7d7d7;
230 | }
231 |
232 | article.post {
233 | padding-bottom: 50px;
234 |
235 | header h1 {
236 | margin: 20px 0;
237 | }
238 |
239 | footer {
240 | overflow: hidden;
241 | margin: 30px 0;
242 | padding: 0;
243 |
244 | ul {
245 | margin: 30px 0;
246 | padding: 0;
247 | list-style: none;
248 | }
249 |
250 | ul.share li {
251 | float: left;
252 | margin: 0 15px 15px 0;
253 | }
254 | }
255 | }
256 | }
257 | section.content-right {
258 | grid-area: right;
259 | }
260 |
261 | > footer {
262 | grid-area: footer;
263 | font: 400 13px "Open Sans", Serif;
264 | line-height: 34px;
265 | text-align: center;
266 | color: #a6a6a6;
267 | box-shadow: 0px -1px 10px 0px #a6a6a6;
268 |
269 | a {
270 | color: #5498dc;
271 | }
272 | }
273 | }
274 |
275 | .pulsate {
276 | opacity: 1;
277 | bottom: 0%;
278 | }
279 |
280 | @-webkit-keyframes pulsate {
281 | 0% {
282 | opacity: 0.4;
283 | bottom: 5%;
284 | }
285 | 50% {
286 | opacity: 1;
287 | bottom: 7%;
288 | }
289 | 100% {
290 | opacity: 0.4;
291 | bottom: 5%;
292 | }
293 | }
294 |
295 | @keyframes pulsate {
296 | 0% {
297 | opacity: 0.4;
298 | bottom: 5%;
299 | }
300 | 50% {
301 | opacity: 1;
302 | bottom: 7%;
303 | }
304 | 100% {
305 | opacity: 0.4;
306 | bottom: 5%;
307 | }
308 | }
309 |
310 | .emoji {
311 | height: 1.2em;
312 | }
313 |
314 | p {
315 | line-height: 38px;
316 | word-wrap: break-word;
317 | }
318 |
319 | h1,
320 | h2,
321 | h3,
322 | h4,
323 | h5,
324 | h6,
325 | .h1,
326 | .h2,
327 | .h3,
328 | .h4,
329 | .h5,
330 | .h6 {
331 | margin-top: 0;
332 | font: 700 34px "Domine", Serif !important; // override bootstrap/fa
333 | line-height: 56px;
334 | font-weight: normal;
335 | color: #333;
336 |
337 | a:hover,
338 | p a:hover {
339 | border-bottom: 3px solid #f1ece9;
340 | }
341 | }
342 |
343 | article,
344 | aside,
345 | details,
346 | figcaption,
347 | figure,
348 | footer,
349 | header,
350 | hgroup,
351 | main,
352 | section,
353 | summary {
354 | display: block;
355 | }
356 |
357 | img {
358 | width: auto;
359 | max-width: 100%;
360 | margin: 30px 0;
361 | border-radius: 4px;
362 | }
363 |
364 | // .cover.featured {
365 | // display: block;
366 | // }
367 |
368 | .cover.slideOut {
369 | transform: translateY(-100vh);
370 | transition-duration: 2s;
371 | }
372 |
373 | .cover {
374 | position: absolute;
375 | width: 100vw;
376 | height: 100vh;
377 | z-index: 10;
378 |
379 | .background {
380 | position: absolute;
381 | top: 0px;
382 | left: 0px;
383 | width: 100%;
384 | height: 100%;
385 | background-size: cover;
386 | text-align: center;
387 |
388 | -webkit-transform: translateZ(0);
389 | transform: translateZ(0);
390 | -webkit-transition: -webkit-transform 0.6s ease-in-out;
391 | transition: transform 0.6s ease-in-out;
392 | -webkit-backface-visibility: hidden;
393 | backface-visibility: hidden;
394 |
395 | .background-title {
396 | position: relative;
397 | top: 45vh;
398 | display: inline-block;
399 | font: 800 42px "Domine", Serif;
400 | color: white;
401 | text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
402 | }
403 |
404 | .background-quote {
405 | position: relative;
406 | top: 45vh;
407 | display: block;
408 | font: 400 18px "Comic Sans MS", Serif;
409 | color: #ddd;
410 | text-shadow: 0 0 5px rgba(0, 0, 0, 0.8);
411 | }
412 | }
413 |
414 | .movedown {
415 | font-size: 32px;
416 | position: absolute;
417 | bottom: 5%;
418 | left: 50%;
419 | cursor: pointer;
420 | -webkit-animation: pulsate 4s ease-out;
421 | -webkit-animation-iteration-count: infinite;
422 | animation: pulsate 4s ease-out;
423 | animation-iteration-count: infinite;
424 | opacity: 0.6;
425 | color: #eee;
426 | }
427 | }
428 | .smallbutton {
429 | font: 600 14px "Open Sans", Serif;
430 | display: inline-block;
431 | padding: 11px 26px;
432 | -webkit-transition: all 0.2s ease-in-out;
433 | -moz-transition: all 0.2s ease-in-out;
434 | transition: all 0.2s ease-in-out;
435 | border-radius: 4px;
436 |
437 | i {
438 | margin-right: 15px;
439 | }
440 | }
441 |
442 | .smallbutton.lightgray:hover {
443 | color: #fff;
444 | border: 2px solid #0066d9;
445 | background: #0066d9;
446 | }
447 |
448 | .smallbutton.lightgray {
449 | color: #686868;
450 | border: 2px solid #c3c2c9;
451 | background: #fff;
452 | }
453 |
454 | .smallbutton {
455 | color: #686868;
456 | border: 2px solid #c3c2c9;
457 | background: #fff;
458 | }
459 | .smallbutton.twitter:hover {
460 | color: #fff;
461 | border: 2px solid darken($twitter-color, 15%);
462 | background: $twitter-color;
463 | }
464 |
465 | .smallbutton.googleplus:hover {
466 | color: #fff;
467 | border: 2px solid darken($gplus-color, 15%);
468 | background: $gplus-color;
469 | }
470 |
471 | a {
472 | font-weight: 600;
473 | text-decoration: none;
474 | color: #026ed2;
475 | border-bottom: 3px solid transparent;
476 | background: transparent;
477 | }
478 |
479 | a:hover {
480 | color: #0079ec;
481 | }
482 |
483 | a:active,
484 | a:hover {
485 | outline: 0;
486 | }
487 |
488 | a:focus,
489 | a:hover {
490 | text-decoration: none;
491 | }
492 |
493 | .smallprofile {
494 | display: inline-block;
495 | width: 20px;
496 | height: 20px;
497 | margin: 0 6px 0 11px;
498 | vertical-align: -4px;
499 | border-radius: 20px;
500 | }
501 |
502 | .authorQuote {
503 | display: flex;
504 | height: 230px;
505 |
506 | img {
507 | height: 200px;
508 | }
509 |
510 | blockquote {
511 | margin: 100px 0 80px 15px;
512 | padding-left: 15px;
513 | border-left: 3px solid #ccc;
514 | }
515 | }
516 | section.aboutBody {
517 | margin: 0 0 30px;
518 | padding-top: 10px;
519 | }
520 |
521 | .authorProfile {
522 | overflow: hidden;
523 | border-top: 1px solid #b3b3b3;
524 | border-bottom: 1px solid #b3b3b3;
525 |
526 | .avatar {
527 | display: block;
528 | float: left;
529 | width: 12%;
530 | border-radius: 96px;
531 | margin: 15px 15px 0 0;
532 | }
533 |
534 | .info {
535 | display: block;
536 | float: right;
537 | width: 82%;
538 |
539 | h4 {
540 | font: 600 20px "Open Sans", Serif !important;
541 | margin: 10px 0 0 0;
542 | }
543 | }
544 |
545 | h4 {
546 | margin: 15px 0;
547 | }
548 |
549 | p {
550 | font: 400 14px "Open Sans", Serif;
551 | line-height: 32px;
552 | margin: 15px 0;
553 | }
554 |
555 | .social {
556 | overflow: hidden;
557 | margin: 0;
558 | padding: 10px 0 0 0;
559 | list-style: none;
560 | -webkit-transition: all 0.25s ease-out;
561 | -moz-transition: all 0.25s ease-out;
562 | -o-transition: all 0.25s ease-out;
563 | transition: all 0.25s ease-out;
564 | -webkit-transform: translateZ(0px);
565 |
566 | .twitter {
567 | -webkit-box-shadow: inset 0 0 0 20px #55acee;
568 | box-shadow: inset 0 0 0 20px #55acee;
569 | }
570 | .twitter:hover {
571 | color: #55acee;
572 | -webkit-box-shadow: inset 0 0 0 2px #55acee;
573 | box-shadow: inset 0 0 0 2px #55acee;
574 | }
575 |
576 | .facebook {
577 | -webkit-box-shadow: inset 0 0 0 20px #3b5998;
578 | box-shadow: inset 0 0 0 20px #3b5998;
579 | }
580 | .facebook:hover {
581 | color: #3b5998;
582 | -webkit-box-shadow: inset 0 0 0 2px #3b5998;
583 | box-shadow: inset 0 0 0 2px #3b5998;
584 | }
585 |
586 | .github {
587 | -webkit-box-shadow: inset 0 0 0 20px #171515;
588 | box-shadow: inset 0 0 0 20px #171515;
589 | }
590 | .github:hover {
591 | color: #171515;
592 | -webkit-box-shadow: inset 0 0 0 2px #171515;
593 | box-shadow: inset 0 0 0 2px #171515;
594 | }
595 |
596 | .rss {
597 | -webkit-box-shadow: inset 0 0 0 20px #ee802f;
598 | box-shadow: inset 0 0 0 20px #ee802f;
599 | }
600 | .rss:hover {
601 | color: #ee802f;
602 | -webkit-box-shadow: inset 0 0 0 2px #ee802f;
603 | box-shadow: inset 0 0 0 2px #ee802f;
604 | }
605 |
606 | .youtube {
607 | -webkit-box-shadow: inset 0 0 0 20px #c4302b;
608 | box-shadow: inset 0 0 0 20px #c4302b;
609 | }
610 | .youtube:hover {
611 | color: #c4302b;
612 | -webkit-box-shadow: inset 0 0 0 2px #c4302b;
613 | box-shadow: inset 0 0 0 2px #c4302b;
614 | }
615 |
616 | .googleplus {
617 | -webkit-box-shadow: inset 0 0 0 20px #dd4b39;
618 | box-shadow: inset 0 0 0 20px #dd4b39;
619 | }
620 | .googleplus:hover {
621 | color: #dd4b39;
622 | -webkit-box-shadow: inset 0 0 0 2px #dd4b39;
623 | box-shadow: inset 0 0 0 2px #dd4b39;
624 | }
625 |
626 | .instagram {
627 | -webkit-box-shadow: inset 0 0 0 20px #3f729b;
628 | box-shadow: inset 0 0 0 20px #3f729b;
629 | }
630 | .instagram:hover {
631 | color: #3f729b;
632 | -webkit-box-shadow: inset 0 0 0 2px #3f729b;
633 | box-shadow: inset 0 0 0 2px #3f729b;
634 | }
635 |
636 | .pinterest {
637 | -webkit-box-shadow: inset 0 0 0 20px #c8232c;
638 | box-shadow: inset 0 0 0 20px #c8232c;
639 | }
640 | .pinterest:hover {
641 | color: #c8232c;
642 | -webkit-box-shadow: inset 0 0 0 2px #c8232c;
643 | box-shadow: inset 0 0 0 2px #c8232c;
644 | }
645 |
646 | .stackoverflow {
647 | -webkit-box-shadow: inset 0 0 0 20px #fe7a15;
648 | box-shadow: inset 0 0 0 20px #fe7a15;
649 | }
650 | .stackoverflow:hover {
651 | color: #c8232c;
652 | -webkit-box-shadow: inset 0 0 0 2px #fe7a15;
653 | box-shadow: inset 0 0 0 2px #fe7a15;
654 | }
655 |
656 | .linkedin {
657 | -webkit-box-shadow: inset 0 0 0 20px #0e76a8;
658 | box-shadow: inset 0 0 0 20px #0e76a8;
659 | }
660 | .linkedin:hover {
661 | color: #0e76a8;
662 | -webkit-box-shadow: inset 0 0 0 2px #0e76a8;
663 | box-shadow: inset 0 0 0 2px #0e76a8;
664 | }
665 | }
666 |
667 | ul {
668 | margin: 0 10px 10px 0;
669 | }
670 |
671 | li {
672 | float: left;
673 | margin: 0 10px 10px 0;
674 | padding: 0;
675 |
676 | a {
677 | display: inline-block;
678 | width: 40px;
679 | padding: 9px 0;
680 | -webkit-transition: all 0.25s ease-out;
681 | -moz-transition: all 0.25s ease-out;
682 | -o-transition: all 0.25s ease-out;
683 | transition: all 0.25s ease-out;
684 | text-align: center;
685 | color: #fff;
686 | border-radius: 28px;
687 | }
688 | }
689 | }
690 |
691 | .profile {
692 | position: fixed;
693 | z-index: 400;
694 | top: 130px;
695 | right: 5%;
696 | width: 15%;
697 | -webkit-backface-visibility: hidden;
698 | -webkit-transition: all 0.3s ease-in-out;
699 | -moz-transition: all 0.3s ease-in-out;
700 | transition: all 0.3s ease-in-out;
701 | }
702 |
703 | .profile.stuck {
704 | opacity: 0.5;
705 | }
706 | .profile:hover {
707 | opacity: 1;
708 | }
709 | .profile.hide {
710 | opacity: 0;
711 | }
712 | .profile .profileimage {
713 | width: 96px;
714 | height: 96px;
715 | margin: 10px 0;
716 | border-radius: 96px;
717 | }
718 | .profile h4 {
719 | margin: 15px 0;
720 | }
721 | .profile ul {
722 | float: left;
723 | overflow: hidden;
724 | margin: 0;
725 | padding: 0;
726 | }
727 | .profile ul li {
728 | float: left;
729 | list-style: none;
730 | }
731 | .profile strong {
732 | font: 600 14px "Open Sans", Serif;
733 | margin: 20px 0 10px;
734 | color: #3a3a3a;
735 | }
736 | .profile p {
737 | font: 400 15px "Open Sans", Serif;
738 | line-height: 28px;
739 | clear: both;
740 | overflow: hidden;
741 | max-height: 136px;
742 | margin: 0 0 10px 0;
743 | text-overflow: ellipsis;
744 | }
745 | .profile p a {
746 | color: #3b7bbf;
747 | }
748 | .profile hr {
749 | width: 100%;
750 | height: 1px;
751 | margin: 10px 0;
752 | border: 0;
753 | background: #b6b6b6;
754 | }
755 | .profile .tweet {
756 | font: 600 12px "Open Sans", Serif;
757 | line-height: 24px;
758 | margin: 20px 0;
759 | }
760 | .profile .tweet span {
761 | display: block;
762 | text-align: left;
763 | color: #848484;
764 | }
765 | @media screen and (max-width: 1350px) {
766 | .profile {
767 | opacity: 0;
768 | }
769 | }
770 | @media screen and (max-width: 1330px) {
771 | .profile {
772 | display: none !important;
773 | }
774 | }
775 | @media screen and (max-height: 640px) {
776 | .profile {
777 | display: none !important;
778 | }
779 | }
780 | .postprofile {
781 | overflow: hidden;
782 | margin: 30px 0;
783 | padding: 30px 0;
784 | border-top: 1px solid #b3b3b3;
785 | border-bottom: 1px solid #b3b3b3;
786 | }
787 | .postprofile .author {
788 | display: block;
789 | float: left;
790 | width: 12%;
791 | border-radius: 96px;
792 | }
793 | .postprofile .info {
794 | display: block;
795 | float: right;
796 | width: 82%;
797 | }
798 | .postprofile h4 {
799 | margin: 15px 0;
800 | }
801 | .postprofile p {
802 | font: 400 14px "Open Sans", Serif;
803 | line-height: 32px;
804 | margin: 15px 0;
805 | }
806 | .postprofile .social {
807 | overflow: hidden;
808 | margin: 20px 0 0;
809 | padding: 0;
810 | list-style: none;
811 | -webkit-transition: all 0.25s ease-out;
812 | -moz-transition: all 0.25s ease-out;
813 | -o-transition: all 0.25s ease-out;
814 | transition: all 0.25s ease-out;
815 | -webkit-transform: translateZ(0);
816 | }
817 | .postprofile ul {
818 | margin: 0 10px 10px 0;
819 | }
820 | .postprofile li {
821 | float: left;
822 | margin: 0 10px 10px 0;
823 | padding: 0;
824 | }
825 | .postprofile li a {
826 | display: inline-block;
827 | width: 40px;
828 | padding: 9px 0;
829 | -webkit-transition: all 0.25s ease-out;
830 | -moz-transition: all 0.25s ease-out;
831 | -o-transition: all 0.25s ease-out;
832 | transition: all 0.25s ease-out;
833 | text-align: center;
834 | color: #fff;
835 | border-radius: 41px;
836 | }
837 | @media screen and (max-width: 320px) {
838 | .postprofile .author {
839 | float: none;
840 | width: 25%;
841 | }
842 | .postprofile .info {
843 | float: none;
844 | width: 100%;
845 | }
846 | }
847 |
848 | .smallsocial {
849 | display: block;
850 | width: 32px;
851 | height: 32px;
852 | margin: 0 3px 10px;
853 | text-align: center;
854 | color: #606060;
855 | background: #efefef;
856 | border-radius: 36px;
857 | -webkit-transition: all 0.2s ease-in-out;
858 | -moz-transition: all 0.2s ease-in-out;
859 | transition: all 0.2s ease-in-out;
860 |
861 | i {
862 | position: relative;
863 | vertical-align: -5px;
864 | }
865 | }
866 |
867 | .smallsocial.twitter:hover {
868 | color: #fff;
869 | background: #55acee;
870 | }
871 |
872 | .smallsocial.facebook:hover {
873 | color: #fff;
874 | background: #3b5998;
875 | }
876 |
877 | .smallsocial.googleplus:hover {
878 | color: #fff;
879 | background: #dd4b39;
880 | }
881 |
882 | .smallsocial.github:hover {
883 | color: #fff;
884 | background: #171515;
885 | }
886 |
887 | .smallsocial.instagram:hover {
888 | color: #fff;
889 | background: #3f729b;
890 | }
891 |
892 | .smallsocial.youtube:hover {
893 | color: #fff;
894 | background: #c4302b;
895 | }
896 |
897 | .smallsocial.pinterest:hover {
898 | color: #fff;
899 | background: #c8232c;
900 | }
901 |
902 | .smallsocial.linkedin:hover {
903 | color: #fff;
904 | background: #0e76a8;
905 | }
906 |
907 | .smallsocial.skype:hover {
908 | color: #fff;
909 | background: #00aff0;
910 | }
911 |
912 | .smallsocial.tumblr:hover {
913 | color: #fff;
914 | background: #34526f;
915 | }
916 |
917 | .smallsocial.stackoverflow:hover {
918 | color: #fff;
919 | background: #fe7a15;
920 | }
921 |
922 | .smallsocial.rss:hover {
923 | color: #fff;
924 | background: #ee802f;
925 | }
926 |
927 | .eta {
928 | display: none;
929 | position: fixed;
930 | bottom: 20px;
931 | right: 20px;
932 | z-index: 500;
933 | background-color: #f0f0f0;
934 | color: #808080;
935 | border: 1px solid $eta-border-color;
936 | border-radius: 3px;
937 | font-family: Georgia;
938 | font-size: 12px;
939 | text-transform: uppercase;
940 | letter-spacing: 1px;
941 | padding: 10px 20px;
942 | }
943 |
944 | .eta:after {
945 | content: " ";
946 | position: absolute;
947 | top: 50%;
948 | right: -8px;
949 | height: 0;
950 | width: 0;
951 | margin-top: -4px;
952 | border: 4px solid transparent;
953 | border-left-color: $eta-border-color; /* #F0F0F0; */
954 | }
955 |
--------------------------------------------------------------------------------
/src/styles/_post.scss:
--------------------------------------------------------------------------------
1 | header {
2 | margin-bottom: 25px;
3 | }
4 |
--------------------------------------------------------------------------------
/src/styles/normalize.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v2.1.3 | MIT License | git.io/normalize */
2 |
3 | /* ==========================================================================
4 | HTML5 display definitions
5 | ========================================================================== */
6 |
7 | /**
8 | * Correct `block` display not defined in IE 8/9.
9 | */
10 |
11 | article,
12 | aside,
13 | details,
14 | figcaption,
15 | figure,
16 | footer,
17 | header,
18 | hgroup,
19 | main,
20 | nav,
21 | section,
22 | summary {
23 | display: block;
24 | }
25 |
26 | /**
27 | * Correct `inline-block` display not defined in IE 8/9.
28 | */
29 |
30 | audio,
31 | canvas,
32 | video {
33 | display: inline-block;
34 | }
35 |
36 | /**
37 | * Prevent modern browsers from displaying `audio` without controls.
38 | * Remove excess height in iOS 5 devices.
39 | */
40 |
41 | audio:not([controls]) {
42 | display: none;
43 | height: 0;
44 | }
45 |
46 | /**
47 | * Address `[hidden]` styling not present in IE 8/9.
48 | * Hide the `template` element in IE, Safari, and Firefox < 22.
49 | */
50 |
51 | [hidden],
52 | template {
53 | display: none;
54 | }
55 |
56 | /* ==========================================================================
57 | Base
58 | ========================================================================== */
59 |
60 | /**
61 | * 1. Set default font family to sans-serif.
62 | * 2. Prevent iOS text size adjust after orientation change, without disabling
63 | * user zoom.
64 | */
65 |
66 | html {
67 | font-family: sans-serif; /* 1 */
68 | -ms-text-size-adjust: 100%; /* 2 */
69 | -webkit-text-size-adjust: 100%; /* 2 */
70 | }
71 |
72 | /**
73 | * Remove default margin.
74 | */
75 |
76 | body {
77 | margin: 0;
78 | }
79 |
80 | /* ==========================================================================
81 | Links
82 | ========================================================================== */
83 |
84 | /**
85 | * Remove the gray background color from active links in IE 10.
86 | */
87 |
88 | a {
89 | background: transparent;
90 | }
91 |
92 | /**
93 | * Address `outline` inconsistency between Chrome and other browsers.
94 | */
95 |
96 | a:focus {
97 | outline: thin dotted;
98 | }
99 |
100 | /**
101 | * Improve readability when focused and also mouse hovered in all browsers.
102 | */
103 |
104 | a:active,
105 | a:hover {
106 | outline: 0;
107 | }
108 |
109 | /* ==========================================================================
110 | Typography
111 | ========================================================================== */
112 |
113 | /**
114 | * Address variable `h1` font-size and margin within `section` and `article`
115 | * contexts in Firefox 4+, Safari 5, and Chrome.
116 | */
117 |
118 | h1 {
119 | font-size: 2em;
120 | margin: 0.67em 0;
121 | }
122 |
123 | /**
124 | * Address styling not present in IE 8/9, Safari 5, and Chrome.
125 | */
126 |
127 | abbr[title] {
128 | border-bottom: 1px dotted;
129 | }
130 |
131 | /**
132 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome.
133 | */
134 |
135 | b,
136 | strong {
137 | font-weight: bold;
138 | }
139 |
140 | /**
141 | * Address styling not present in Safari 5 and Chrome.
142 | */
143 |
144 | dfn {
145 | font-style: italic;
146 | }
147 |
148 | /**
149 | * Address differences between Firefox and other browsers.
150 | */
151 |
152 | hr {
153 | -moz-box-sizing: content-box;
154 | box-sizing: content-box;
155 | height: 0;
156 | }
157 |
158 | /**
159 | * Address styling not present in IE 8/9.
160 | */
161 |
162 | mark {
163 | background: #ff0;
164 | color: #000;
165 | }
166 |
167 | /**
168 | * Correct font family set oddly in Safari 5 and Chrome.
169 | */
170 |
171 | code,
172 | kbd,
173 | pre,
174 | samp {
175 | font-family: monospace, serif;
176 | font-size: 1em;
177 | }
178 |
179 | /**
180 | * Improve readability of pre-formatted text in all browsers.
181 | */
182 |
183 | pre {
184 | white-space: pre-wrap;
185 | }
186 |
187 | /**
188 | * Set consistent quote types.
189 | */
190 |
191 | q {
192 | quotes: "\201C" "\201D" "\2018" "\2019";
193 | }
194 |
195 | /**
196 | * Address inconsistent and variable font size in all browsers.
197 | */
198 |
199 | small {
200 | font-size: 80%;
201 | }
202 |
203 | /**
204 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
205 | */
206 |
207 | sub,
208 | sup {
209 | font-size: 75%;
210 | line-height: 0;
211 | position: relative;
212 | vertical-align: baseline;
213 | }
214 |
215 | sup {
216 | top: -0.5em;
217 | }
218 |
219 | sub {
220 | bottom: -0.25em;
221 | }
222 |
223 | /* ==========================================================================
224 | Embedded content
225 | ========================================================================== */
226 |
227 | /**
228 | * Remove border when inside `a` element in IE 8/9.
229 | */
230 |
231 | img {
232 | border: 0;
233 | }
234 |
235 | /**
236 | * Correct overflow displayed oddly in IE 9.
237 | */
238 |
239 | svg:not(:root) {
240 | overflow: hidden;
241 | }
242 |
243 | /* ==========================================================================
244 | Figures
245 | ========================================================================== */
246 |
247 | /**
248 | * Address margin not present in IE 8/9 and Safari 5.
249 | */
250 |
251 | figure {
252 | margin: 0;
253 | }
254 |
255 | /* ==========================================================================
256 | Forms
257 | ========================================================================== */
258 |
259 | /**
260 | * Define consistent border, margin, and padding.
261 | */
262 |
263 | fieldset {
264 | border: 1px solid #c0c0c0;
265 | margin: 0 2px;
266 | padding: 0.35em 0.625em 0.75em;
267 | }
268 |
269 | /**
270 | * 1. Correct `color` not being inherited in IE 8/9.
271 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
272 | */
273 |
274 | legend {
275 | border: 0; /* 1 */
276 | padding: 0; /* 2 */
277 | }
278 |
279 | /**
280 | * 1. Correct font family not being inherited in all browsers.
281 | * 2. Correct font size not being inherited in all browsers.
282 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome.
283 | */
284 |
285 | button,
286 | input,
287 | select,
288 | textarea {
289 | font-family: inherit; /* 1 */
290 | font-size: 100%; /* 2 */
291 | margin: 0; /* 3 */
292 | }
293 |
294 | /**
295 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
296 | * the UA stylesheet.
297 | */
298 |
299 | button,
300 | input {
301 | line-height: normal;
302 | }
303 |
304 | /**
305 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
306 | * All other form control elements do not inherit `text-transform` values.
307 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+.
308 | * Correct `select` style inheritance in Firefox 4+ and Opera.
309 | */
310 |
311 | button,
312 | select {
313 | text-transform: none;
314 | }
315 |
316 | /**
317 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
318 | * and `video` controls.
319 | * 2. Correct inability to style clickable `input` types in iOS.
320 | * 3. Improve usability and consistency of cursor style between image-type
321 | * `input` and others.
322 | */
323 |
324 | button,
325 | html input[type="button"], /* 1 */
326 | input[type="reset"],
327 | input[type="submit"] {
328 | -webkit-appearance: button; /* 2 */
329 | cursor: pointer; /* 3 */
330 | }
331 |
332 | /**
333 | * Re-set default cursor for disabled elements.
334 | */
335 |
336 | button[disabled],
337 | html input[disabled] {
338 | cursor: default;
339 | }
340 |
341 | /**
342 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
343 | * 2. Remove excess padding in IE 8/9/10.
344 | */
345 |
346 | input[type="checkbox"],
347 | input[type="radio"] {
348 | box-sizing: border-box; /* 1 */
349 | padding: 0; /* 2 */
350 | }
351 |
352 | /**
353 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
354 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
355 | * (include `-moz` to future-proof).
356 | */
357 |
358 | input[type="search"] {
359 | -webkit-appearance: textfield; /* 1 */
360 | -moz-box-sizing: content-box;
361 | -webkit-box-sizing: content-box; /* 2 */
362 | box-sizing: content-box;
363 | }
364 |
365 | /**
366 | * Remove inner padding and search cancel button in Safari 5 and Chrome
367 | * on OS X.
368 | */
369 |
370 | input[type="search"]::-webkit-search-cancel-button,
371 | input[type="search"]::-webkit-search-decoration {
372 | -webkit-appearance: none;
373 | }
374 |
375 | /**
376 | * Remove inner padding and border in Firefox 4+.
377 | */
378 |
379 | button::-moz-focus-inner,
380 | input::-moz-focus-inner {
381 | border: 0;
382 | padding: 0;
383 | }
384 |
385 | /**
386 | * 1. Remove default vertical scrollbar in IE 8/9.
387 | * 2. Improve readability and alignment in all browsers.
388 | */
389 |
390 | textarea {
391 | overflow: auto; /* 1 */
392 | vertical-align: top; /* 2 */
393 | }
394 |
395 | /* ==========================================================================
396 | Tables
397 | ========================================================================== */
398 |
399 | /**
400 | * Remove most spacing between table cells.
401 | */
402 |
403 | table {
404 | border-collapse: collapse;
405 | border-spacing: 0;
406 | }
--------------------------------------------------------------------------------
/src/styles/site.scss:
--------------------------------------------------------------------------------
1 | // Bootstrap v4.0.0-beta.3 (https://getbootstrap.com)
2 |
3 | // Fix for bootstrap beta, due to variable used by variables being in new functions
4 | @import "../../node_modules/bootstrap/scss/functions.scss";
5 |
6 | @import "../../node_modules/bootstrap/scss/variables";
7 | @import "variables";
8 | // Core variables and mixins
9 | @import "../../node_modules/bootstrap/scss/mixins";
10 | // Reset and dependencies
11 | // @import '../../node_modules/bootstrap/scss/normalize';
12 | // @import '../../node_modules/bootstrap/scss/print';
13 | // Core CSS
14 | @import "../../node_modules/bootstrap/scss/reboot";
15 | // @import '../../node_modules/bootstrap/scss/type';
16 | // @import '../../node_modules/bootstrap/scss/images';
17 | // @import '../../node_modules/bootstrap/scss/code';
18 | @import "../../node_modules/bootstrap/scss/grid";
19 | // @import '../../node_modules/bootstrap/scss/tables';
20 | // @import '../../node_modules/bootstrap/scss/forms';
21 | @import "../../node_modules/bootstrap/scss/buttons";
22 | // Components
23 | @import "../../node_modules/bootstrap/scss/transitions";
24 | // @import '../../node_modules/bootstrap/scss/dropdown';
25 | // @import '../../node_modules/bootstrap/scss/button-group';
26 | // @import '../../node_modules/bootstrap/scss/input-group';
27 | // @import '../../node_modules/bootstrap/scss/custom-forms';
28 | @import "../../node_modules/bootstrap/scss/nav";
29 | @import "../../node_modules/bootstrap/scss/navbar";
30 | // @import '../../node_modules/bootstrap/scss/card';
31 | // @import '../../node_modules/bootstrap/scss/breadcrumb';
32 | // @import '../../node_modules/bootstrap/scss/pagination';
33 | // @import '../../node_modules/bootstrap/scss/badge';
34 | // @import '../../node_modules/bootstrap/scss/jumbotron';
35 | // @import '../../node_modules/bootstrap/scss/alert';
36 | // @import '../../node_modules/bootstrap/scss/progress';
37 | // @import '../../node_modules/bootstrap/scss/media';
38 | // @import '../../node_modules/bootstrap/scss/list-group';
39 | // @import '../../node_modules/bootstrap/scss/responsive-embed';
40 | // @import '../../node_modules/bootstrap/scss/close';
41 | // Components w/ jQuery Plugins
42 | // @import '../../node_modules/bootstrap/scss/modal';
43 | // @import '../../node_modules/bootstrap/scss/tooltip';
44 | // @import '../../node_modules/bootstrap/scss/popover';
45 | // @import '../../node_modules/bootstrap/scss/carousel';
46 | // Utility classes
47 | @import "../../node_modules/bootstrap/scss/utilities";
48 |
49 | /*!
50 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
51 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
52 | */
53 |
54 | @import "../../node_modules/font-awesome/scss/variables";
55 | @import "../../node_modules/font-awesome/scss/mixins";
56 | @import "../../node_modules/font-awesome/scss/path";
57 | @import "../../node_modules/font-awesome/scss/core";
58 | // @import '../../node_modules/font-awesome/scss/larger';
59 | // @import '../../node_modules/font-awesome/scss/fixed-width';
60 | // @import '../../node_modules/font-awesome/scss/list';
61 | // @import '../../node_modules/font-awesome/scss/bordered-pulled';
62 | // @import '../../node_modules/font-awesome/scss/animated';
63 | // @import '../../node_modules/font-awesome/scss/rotated-flipped';
64 | // @import '../../node_modules/font-awesome/scss/stacked';
65 | @import "../../node_modules/font-awesome/scss/icons";
66 | @import "../../node_modules/font-awesome/scss/screen-reader";
67 | @import "aurelia";
68 | @import "../../node_modules/highlight.js/styles/github.css";
69 |
70 | @import "./post";
71 |
--------------------------------------------------------------------------------
/test/jest-pretest.ts:
--------------------------------------------------------------------------------
1 | import 'aurelia-polyfills';
2 | import {Options} from 'aurelia-loader-nodejs';
3 | import {globalize} from 'aurelia-pal-nodejs';
4 | import * as path from 'path';
5 | Options.relativeToDir = path.join(__dirname, 'unit');
6 | globalize();
7 |
--------------------------------------------------------------------------------
/test/unit/__snapshots__/welcome.spec.ts.snap:
--------------------------------------------------------------------------------
1 | exports[`WelcomeComponent should render correctly 1`] = `
2 | "
3 |
4 | Welcome to the Aurelia Navigation App
5 |
20 |
21 | "
22 | `;
23 |
--------------------------------------------------------------------------------
/test/unit/app.spec.ts:
--------------------------------------------------------------------------------
1 | import { App } from '../../src/app';
2 | import { EventAggregator } from 'aurelia-event-aggregator';
3 |
4 | class RouterStub {
5 | routes;
6 |
7 | configure(handler) {
8 | handler(this);
9 | }
10 |
11 | map(routes) {
12 | this.routes = routes;
13 | }
14 | }
15 |
16 | describe('the App module', () => {
17 | let sut: App;
18 | let mockedRouter: any;
19 |
20 | beforeEach(() => {
21 | mockedRouter = new RouterStub();
22 | let ea = new EventAggregator();
23 | sut = new App(ea);
24 | sut.configureRouter(mockedRouter, mockedRouter);
25 | });
26 |
27 | it('contains a router property', () => {
28 | expect(sut.router).toBeDefined();
29 | });
30 |
31 | it('configures the router title', () => {
32 | expect((sut.router as any).title).toEqual('Aurelia');
33 | });
34 |
35 | it('should have a welcome route', () => {
36 | expect(sut.router.routes).toContainEqual({ route: ['', 'welcome'], name: 'welcome', moduleId: './welcome', nav: true, title: 'Welcome' });
37 | });
38 |
39 | it('should have a users route', () => {
40 | expect(sut.router.routes).toContainEqual({ route: 'users', name: 'users', moduleId: './users', nav: true, title: 'Github Users' });
41 | });
42 |
43 | it('should have a child router route', () => {
44 | expect(sut.router.routes).toContainEqual({ route: 'child-router', name: 'child-router', moduleId: './child-router', nav: true, title: 'Child Router' });
45 | });
46 | });
47 |
--------------------------------------------------------------------------------
/test/unit/child-router.spec.ts:
--------------------------------------------------------------------------------
1 | // import {ChildRouter} from '../../src/child-router';
2 |
3 | // class RouterStub {
4 | // routes;
5 |
6 | // configure(handler) {
7 | // handler(this);
8 | // }
9 |
10 | // map(routes) {
11 | // this.routes = routes;
12 | // }
13 | // }
14 |
15 | // describe('the Child Router module', () => {
16 | // let sut: ChildRouter;
17 | // let mockedRouter: any;
18 |
19 | // beforeEach(() => {
20 | // mockedRouter = new RouterStub();
21 | // sut = new ChildRouter();
22 | // sut.configureRouter(mockedRouter, mockedRouter);
23 | // });
24 |
25 | // it('contains a router property', () => {
26 | // expect(sut.router).toBeDefined();
27 | // });
28 |
29 | // it('configures the heading', () => {
30 | // expect(sut.heading).toEqual('Child Router');
31 | // });
32 |
33 | // it('should have a welcome route', () => {
34 | // expect(sut.router.routes).toContainEqual({ route: ['', 'welcome'], name: 'welcome', moduleId: './welcome', nav: true, title: 'Welcome' });
35 | // });
36 |
37 | // it('should have a users route', () => {
38 | // expect(sut.router.routes).toContainEqual({ route: 'users', name: 'users', moduleId: './users', nav: true, title: 'Github Users' });
39 | // });
40 |
41 | // it('should have a child router route', () => {
42 | // expect(sut.router.routes).toContainEqual({ route: 'child-router', name: 'child-router', moduleId: './child-router', nav: true, title: 'Child Router' });
43 | // });
44 | // });
45 |
--------------------------------------------------------------------------------
/test/unit/users.spec.ts:
--------------------------------------------------------------------------------
1 | // import {HttpClient} from 'aurelia-fetch-client';
2 | // import {Users} from '../../src/users';
3 |
4 | // class HttpStub extends HttpClient {
5 | // url: string;
6 | // itemStub: any;
7 |
8 | // fetch(url: string): any {
9 | // var response = this.itemStub;
10 | // this.url = url;
11 | // return new Promise((resolve) => {
12 | // resolve({ json: () => response });
13 | // });
14 | // }
15 |
16 | // configure(config) {
17 | // return this;
18 | // }
19 | // }
20 |
21 | // describe('the Users module', () => {
22 | // it('sets fetch response to users', async () => {
23 | // var itemStubs = [1];
24 | // var itemFake = [2];
25 |
26 | // var getHttp = () => {
27 | // var http = new HttpStub();
28 | // http.itemStub = itemStubs;
29 | // return http;
30 | // };
31 |
32 | // var sut = new Users(getHttp);
33 |
34 | // await sut.activate()
35 | // expect(sut.users).toBe(itemStubs);
36 | // expect(sut.users).not.toBe(itemFake);
37 | // });
38 | // });
39 |
--------------------------------------------------------------------------------
/test/unit/welcome.spec.ts:
--------------------------------------------------------------------------------
1 | import {bootstrap} from 'aurelia-bootstrapper';
2 | import {StageComponent} from 'aurelia-testing';
3 |
4 | describe('WelcomeComponent', () => {
5 | let component;
6 |
7 | beforeEach(async () => {
8 | component = StageComponent
9 | .withResources('../../src/welcome')
10 | .inView('');
11 | await component.create(bootstrap);
12 | });
13 |
14 | it('should render correctly', () => {
15 | expect(document.body.outerHTML).toMatchSnapshot();
16 | })
17 |
18 | it('should render first name', () => {
19 | const nameElement = document.querySelector('#fn') as HTMLInputElement;
20 | expect(nameElement.value).toBe('John');
21 | });
22 |
23 | afterEach(() => {
24 | component.dispose();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "emitDecoratorMetadata": true,
6 | "experimentalDecorators": true,
7 | "moduleResolution": "node",
8 | "allowJs": true,
9 | "sourceRoot": "src",
10 | "baseUrl": "src",
11 | "lib": [
12 | "es2017", "dom"
13 | ]
14 | },
15 | "compileOnSave": true,
16 | "exclude": [
17 | "node_modules",
18 | "docs",
19 | "dist"
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 |
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const HtmlWebpackPlugin = require("html-webpack-plugin");
3 | const CopyWebpackPlugin = require("copy-webpack-plugin");
4 | const ExtractTextPlugin = require("extract-text-webpack-plugin");
5 | const project = require("./aurelia_project/aurelia.json");
6 | const { AureliaPlugin, ModuleDependenciesPlugin } = require("aurelia-webpack-plugin");
7 | const { optimize: { CommonsChunkPlugin, UglifyJsPlugin }, ProvidePlugin } = require("webpack");
8 | const { TsConfigPathsPlugin, CheckerPlugin } = require("awesome-typescript-loader");
9 | const ApplicationConfig = require("./src/config/application.config.json");
10 |
11 | // config helpers:
12 | const ensureArray = config => (config && (Array.isArray(config) ? config : [config])) || [];
13 | const when = (condition, config, negativeConfig) => (condition ? ensureArray(config) : ensureArray(negativeConfig));
14 |
15 | // primary config:
16 | const title = ApplicationConfig.siteTitle || ApplicationConfig.siteName; // 'Aurelia Navigation Skeleton';
17 | const description = ApplicationConfig.siteDescription || null; // from old config
18 | const outDir = path.resolve(__dirname, project.platform.output);
19 | const srcDir = path.resolve(__dirname, "src");
20 | const nodeModulesDir = path.resolve(__dirname, "node_modules");
21 | const baseUrl = "/";
22 |
23 | const cssRules = [{ loader: "css-loader" }];
24 |
25 | module.exports = ({ production, server, extractCss, coverage } = {}) => ({
26 | resolve: {
27 | extensions: [".ts", ".js"],
28 | modules: [srcDir, "node_modules"]
29 | },
30 | entry: {
31 | app: ["aurelia-bootstrapper"],
32 | vendor: ["bluebird"]
33 | },
34 | output: {
35 | path: outDir,
36 | publicPath: baseUrl,
37 | filename: production ? "[name].[chunkhash].bundle.js" : "[name].[hash].bundle.js",
38 | sourceMapFilename: production ? "[name].[chunkhash].bundle.map" : "[name].[hash].bundle.map",
39 | chunkFilename: production ? "[name].[chunkhash].chunk.js" : "[name].[hash].chunk.js"
40 | },
41 |
42 | watch: true,
43 |
44 | devServer: {
45 | contentBase: outDir,
46 | // serve index.html for all 404 (required for push-state)
47 | historyApiFallback: true
48 | },
49 | devtool: production ? "nosources-source-map" : "cheap-module-eval-source-map",
50 | module: {
51 | rules: [
52 | // CSS required in JS/TS files should use the style-loader that auto-injects it into the website
53 | // only when the issuer is a .js/.ts file, so the loaders are not applied inside html templates
54 | {
55 | test: /\.css$/i,
56 | issuer: [{ not: [{ test: /\.html$/i }] }],
57 | use: extractCss
58 | ? ExtractTextPlugin.extract({
59 | fallback: "style-loader",
60 | use: cssRules
61 | })
62 | : ["style-loader", ...cssRules]
63 | },
64 | {
65 | test: /\.css$/i,
66 | issuer: [{ test: /\.html$/i }],
67 | // CSS required in templates cannot be extracted safely
68 | // because Aurelia would try to require it again in runtime
69 | use: cssRules
70 | },
71 | { test: /\.scss$/, use: ["style-loader", "css-loader", "sass-loader"], issuer: /\.[tj]s$/i },
72 | { test: /\.scss$/, use: ["css-loader", "sass-loader"], issuer: /\.html?$/i },
73 |
74 | { test: /\.html$/i, loader: "html-loader" },
75 | { test: /\.ts$/i, loader: "awesome-typescript-loader", exclude: nodeModulesDir },
76 | { test: /\.json$/i, loader: "json-loader" },
77 | // Images
78 | { test: /\images.(png|gif|jpg)$/, loader: "url" },
79 |
80 | // use Bluebird as the global Promise implementation:
81 | { test: /[\/\\]node_modules[\/\\]bluebird[\/\\].+\.js$/, loader: "expose-loader?Promise" },
82 | // embed small images and fonts as Data Urls and larger ones as files:
83 | { test: /\.(png|gif|jpg|cur)$/i, loader: "url-loader", options: { limit: 8192 } },
84 | {
85 | test: /\.woff2(\?v=[0-9]\.[0-9]\.[0-9])?$/i,
86 | loader: "url-loader",
87 | options: { limit: 10000, mimetype: "application/font-woff2" }
88 | },
89 | {
90 | test: /\.woff(\?v=[0-9]\.[0-9]\.[0-9])?$/i,
91 | loader: "url-loader",
92 | options: { limit: 10000, mimetype: "application/font-woff" }
93 | },
94 | // load these fonts normally, as files:
95 | { test: /\.(ttf|eot|svg|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/i, loader: "file-loader" },
96 | ...when(coverage, {
97 | test: /\.[jt]s$/i,
98 | loader: "istanbul-instrumenter-loader",
99 | include: srcDir,
100 | exclude: [/\.{spec,test}\.[jt]s$/i],
101 | enforce: "post",
102 | options: { esModules: true }
103 | })
104 | ]
105 | },
106 | plugins: [
107 | new AureliaPlugin(),
108 | new ProvidePlugin({
109 | Promise: "bluebird",
110 | $: "jquery",
111 | jQuery: "jquery",
112 | "window.jQuery": "jquery",
113 | Popper: ["popper.js", "default"]
114 | }),
115 | new ModuleDependenciesPlugin({
116 | "aurelia-testing": ["./compile-spy", "./view-spy"]
117 | }),
118 | new TsConfigPathsPlugin(),
119 | new CheckerPlugin(),
120 | new HtmlWebpackPlugin({
121 | template: "index.ejs",
122 | // from old config
123 | minify: production
124 | ? {
125 | removeComments: true,
126 | collapseWhitespace: true
127 | }
128 | : undefined,
129 | metadata: {
130 | // available in index.ejs //
131 | title,
132 | description,
133 | server,
134 | baseUrl
135 | }
136 | }),
137 | ...when(
138 | extractCss,
139 | new ExtractTextPlugin({
140 | filename: production ? "[contenthash].css" : "[id].css",
141 | allChunks: true
142 | })
143 | ),
144 | ...when(
145 | production,
146 | new CommonsChunkPlugin({
147 | name: ["common"]
148 | })
149 | ),
150 | new CopyWebpackPlugin([
151 | { from: "src/images/favicon.ico", to: "favicon.ico" },
152 | { from: "src/images", to: "./images" },
153 | { from: "404.html", to: "./" },
154 | { from: ".nojekyll", to: "./" },
155 | { from: "CNAME", to: "./" }
156 | ]),
157 | ...when(
158 | production,
159 | new UglifyJsPlugin({
160 | sourceMap: true
161 | })
162 | )
163 | ]
164 | });
165 |
--------------------------------------------------------------------------------
Post a comment
4 |You are logged in as ${currentUser.name} ${currentUser.provider}
7 | 8 | 9 | 10 | Login via Facebook 11 | Login via Google 12 | Login via Twitter 13 | 14 |