├── custom.d.ts
├── src
├── webpack
│ ├── index.ts
│ ├── build.ts
│ ├── serve.ts
│ └── index.html
├── index.ts
├── views
│ ├── index.ts
│ ├── ui-guide-preview.component.ts
│ ├── root.component.ts
│ ├── entry.component.ts
│ └── components.component.ts
├── services
│ ├── index.ts
│ ├── sandbox.service.ts
│ └── ui-guide.service.ts
├── components
│ ├── index.ts
│ ├── prism.component.ts
│ ├── markdown.component.ts
│ ├── ui-guide.component.ts
│ ├── ui-api.component.ts
│ ├── ui-example.component.ts
│ └── guides-list.component.ts
├── routes.ts
├── boostrap.ts
├── ui-guide.module.ts
├── styles.ts
├── cli.ts
├── generate-hosts.ts
├── ui-guide-builder.ts
├── interfaces.ts
└── assets
│ └── flexbox.css
├── .gitignore
├── mocha.opts
├── tsconfig.cli.json
├── tsconfig.esm.json
├── tsconfig.json
├── test-shim.js
├── webpack.testing.js
├── tslint.json
├── webpack.config.js
├── package.json
└── README.md
/custom.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*'
2 |
--------------------------------------------------------------------------------
/src/webpack/index.ts:
--------------------------------------------------------------------------------
1 | export * from './serve'
2 | export * from './build'
3 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './interfaces'
2 | export { docsFor, createDopeDocs, DocsBuilder} from './ui-guide-builder'
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | *.tmp
4 | DS*
5 | typings
6 | dist
7 | .vscode
8 | *.pid
9 | *.seed
10 | build/Release
11 |
--------------------------------------------------------------------------------
/mocha.opts:
--------------------------------------------------------------------------------
1 | --webpack-config ./webpack.testing.js
2 | --require ./test-shim.js
3 | --ui bdd
4 | --glob *.spec.ts
5 | --recursive
6 | src
7 |
--------------------------------------------------------------------------------
/src/views/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ui-guide-preview.component'
2 | export * from './root.component'
3 | export * from './entry.component'
4 | export * from './components.component'
5 |
--------------------------------------------------------------------------------
/tsconfig.cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./dist/cli"
5 | },
6 | "files": [
7 | "./src/cli.ts"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/src/services/index.ts:
--------------------------------------------------------------------------------
1 | import { OpaqueToken } from '@angular/core'
2 | export * from './ui-guide.service'
3 | export * from './sandbox.service'
4 | export const ENTRY_MARKDOWN = new OpaqueToken('entry_markdown')
--------------------------------------------------------------------------------
/src/components/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ui-guide.component'
2 | export * from './ui-example.component'
3 | export * from './guides-list.component'
4 | export * from './ui-api.component'
5 | export * from './prism.component'
6 | export * from './markdown.component'
7 |
--------------------------------------------------------------------------------
/tsconfig.esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "es2015",
5 | "sourceMap": true,
6 | "moduleResolution": "node",
7 | "emitDecoratorMetadata": true,
8 | "experimentalDecorators": true,
9 | "stripInternal": true,
10 | "declaration": true,
11 | "outDir": "./dist",
12 | "lib": ["es2015", "dom"]
13 | },
14 | "files": [
15 | "./src/index.ts",
16 | "./src/cli.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/src/views/ui-guide-preview.component.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs/Observable'
2 | import { Component } from '@angular/core'
3 | import { ActivatedRoute, Params } from '@angular/router'
4 |
5 | @Component({
6 | selector: 'ui-guide-preview',
7 | template: `
8 |
9 | `,
10 | })
11 | export class UIGuidePreviewView {
12 | id: string = ''
13 |
14 | constructor(route: ActivatedRoute) {
15 | route
16 | .params
17 | .subscribe((params: Params) => {
18 | this.id = params['guideId']
19 | })
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/views/root.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ViewEncapsulation } from '@angular/core'
2 |
3 | @Component({
4 | selector: 'ui-guide-root',
5 | template: '',
6 | styles: [`
7 | html {
8 | font-size: 13px;
9 | }
10 | @media only screen and (min-width: 768px) and (max-width: 1024px) {
11 | html {
12 | font-size: 15px;
13 | }
14 | }
15 |
16 | @media only screen and (min-width: 1025px) {
17 | html {
18 | font-size: 17px;
19 | }
20 | }
21 | `],
22 | encapsulation: ViewEncapsulation.None
23 | })
24 | export class UIGuideRootView {}
25 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "angularCompilerOptions": {
3 | "skipTemplateCodegen": true
4 | },
5 | "compilerOptions": {
6 | "declaration": true,
7 | "emitDecoratorMetadata": true,
8 | "experimentalDecorators": true,
9 | "lib": [
10 | "es2015",
11 | "dom"
12 | ],
13 | "module": "commonjs",
14 | "outDir": "./dist",
15 | "sourceMap": true,
16 | "rootDir": "./src",
17 | "stripInternal": true,
18 | "target": "es5",
19 | "typeRoots": [
20 | "./node_modules/@types",
21 | "./node_modules"
22 | ],
23 | "types": [
24 | "node",
25 | "mocha",
26 | "chai",
27 | "webpack",
28 | "webpack-dev-server",
29 | "commander"
30 | ]
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/routes.ts:
--------------------------------------------------------------------------------
1 | import { ModuleWithProviders } from '@angular/core'
2 | import {Routes, RouterModule} from '@angular/router'
3 | import { UIGuidePreviewView, UIGuideRouterEntryView, ComponentsView } from './views'
4 |
5 | export const routes: Routes = [
6 | {
7 | path: '',
8 | component: UIGuideRouterEntryView,
9 | children: [
10 | {
11 | path: '',
12 | redirectTo: '/components',
13 | pathMatch: 'full'
14 | },
15 | {
16 | path: 'components',
17 | component: ComponentsView
18 | },
19 | {
20 | path: ':guideId',
21 | component: UIGuidePreviewView
22 | }
23 | ]
24 | }
25 | ]
26 |
27 | export const Routing: ModuleWithProviders = RouterModule.forRoot(routes)
28 |
--------------------------------------------------------------------------------
/src/components/prism.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, Renderer2, ElementRef, AfterViewInit } from '@angular/core'
2 | declare var Prism: any
3 |
4 | @Component({
5 | selector: 'prism-block',
6 | template: ``,
7 | })
8 | export class PrismComponent implements AfterViewInit {
9 | @Input() code: string
10 | @Input() language: string
11 |
12 | private preNode: Node
13 | private codeNode: Node
14 | private nativeElement: Node
15 |
16 | ngAfterViewInit () {
17 | this.preNode = this._renderer.createElement('pre')
18 | this.codeNode = this._renderer.createElement('code')
19 | this._renderer.addClass(this.codeNode, 'language-' + this.language)
20 | this._renderer.appendChild(this.nativeElement, this.preNode)
21 | this._renderer.appendChild(this.preNode, this.codeNode)
22 | this.codeNode.textContent = this.code
23 |
24 | Prism.highlightElement(this.codeNode)
25 | }
26 |
27 | constructor(private _renderer: Renderer2, private _el: ElementRef) {
28 | this.nativeElement = _el.nativeElement
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/views/entry.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ViewEncapsulation } from '@angular/core'
2 | import { UIGuideSerivce } from '../services/ui-guide.service'
3 | import { UIGuide } from '../interfaces'
4 | import {sizes, media} from '../styles'
5 |
6 | const tabletUp = `.main {padding-left: ${sizes.sidbarWidth}; padding-top: 0px;}`
7 | const phone = `.main {padding-top: ${sizes.navbarHeight}; padding-left: 0px;}`
8 |
9 | @Component({
10 | selector: 'ui-guide-router-entry-view',
11 | template: `
12 |
18 | `,
19 | styles: [`
20 | .nav-open .main {
21 | display: none;
22 | }
23 | .main {
24 | overflow-x: hidden;
25 | }
26 | ${phone}
27 | ${media.greaterThanPhone(tabletUp)}
28 | `],
29 | encapsulation: ViewEncapsulation.None
30 | })
31 | export class UIGuideRouterEntryView {
32 | uiGuides: UIGuide[] = []
33 |
34 | constructor(uiGuideService: UIGuideSerivce) {
35 | this.uiGuides = uiGuideService.getAllUIGuides()
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/webpack/build.ts:
--------------------------------------------------------------------------------
1 | import { UIGuideBuildConfig } from '../interfaces'
2 | import * as webpack from 'webpack'
3 | import * as path from 'path'
4 |
5 | const HTMLPlugin = require('html-webpack-plugin')
6 | const ProgressPlugin = require('webpack/lib/ProgressPlugin')
7 | const chalk = require('chalk')
8 |
9 | export function build(config: UIGuideBuildConfig) {
10 | const webpackConfig = config.webpackConfig
11 |
12 | webpackConfig.entry = {
13 | main: config.entry
14 | }
15 |
16 | webpackConfig.output = {
17 | path: path.join(process.cwd(), 'dope-docs'),
18 | filename: 'dope-docs.js'
19 | }
20 |
21 | webpackConfig.plugins = webpackConfig.plugins.filter(p => !(p instanceof HTMLPlugin))
22 |
23 | const compiler = webpack(webpackConfig)
24 |
25 | compiler.apply(new ProgressPlugin({
26 | colors: true,
27 | profile: true
28 | }))
29 |
30 | compiler.apply(new HTMLPlugin({
31 | template: path.resolve(__dirname, '../../index.html')
32 | }))
33 |
34 | compiler.run((err, stats) => {
35 | if (err) {
36 | console.error(chalk.red(err))
37 | process.exit(1)
38 | return
39 | }
40 | console.log(chalk.green(stats.toString()))
41 | process.exit(0)
42 | })
43 | }
44 |
--------------------------------------------------------------------------------
/src/components/markdown.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, ViewEncapsulation } from '@angular/core'
2 | import * as marked from 'marked';
3 |
4 | @Component({
5 | selector: 'dope-docs-markdown',
6 | template: `
7 |
8 | `,
9 | encapsulation: ViewEncapsulation.None
10 | })
11 | export class MarkdownComponent {
12 | @Input() markdown: string = ''
13 | md: any
14 | parsedMarkdown: string;
15 |
16 | constructor() {
17 | // hijack the renderer
18 | const renderer = new marked.Renderer();
19 |
20 | // nest code block within pre tags
21 | renderer.code = (code, lang) => {
22 | return `${code}
`;
23 | }
24 |
25 | // nest inline code block within pre tags
26 | renderer.codespan = (code) => {
27 | return `${code}
`;
28 | }
29 | this.md = marked.setOptions({
30 | gfm: true,
31 | tables: true,
32 | breaks: false,
33 | pedantic: false,
34 | sanitize: true,
35 | smartLists: true,
36 | smartypants: false,
37 | renderer: renderer
38 | });
39 | }
40 |
41 | ngOnChanges() {
42 | this.parsedMarkdown = this.md.parse(this.markdown || '');
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/test-shim.js:
--------------------------------------------------------------------------------
1 | require('source-map-support').install({
2 | environment: 'node'
3 | });
4 |
5 | const jsdom = require('jsdom');
6 |
7 | const {window} = new jsdom.JSDOM('');
8 |
9 |
10 | // global.window = global.Window = window
11 | global.document = window.document;
12 | global.mediaMatch = window.mediaMatch;
13 | global.HTMLElement = window.HTMLElement;
14 | global.XMLHttpRequest = window.XMLHttpRequest;
15 | global.Node = window.Node;
16 | // global.scrollTo = window.scrollTo;
17 | // global.history = window.history;
18 | // global.clearInterval = window.clearInterval;
19 | // global.pageYOffset = window.pageYOffset;
20 | // global.sinon = require('sinon');
21 |
22 | require('core-js/es6');
23 | require('core-js/es7/reflect');
24 |
25 | require('zone.js/dist/zone');
26 | require('zone.js/dist/long-stack-trace-zone');
27 | require('zone.js/dist/proxy');
28 | require('zone.js/dist/sync-test');
29 | require('zone.js/dist/async-test');
30 | require('zone.js/dist/fake-async-test');
31 |
32 | var testing = require('@angular/core/testing');
33 | var browser = require('@angular/platform-browser-dynamic/testing');
34 |
35 | testing.TestBed.initTestEnvironment(browser.BrowserDynamicTestingModule, browser.platformBrowserDynamicTesting());
36 |
--------------------------------------------------------------------------------
/webpack.testing.js:
--------------------------------------------------------------------------------
1 | const nodeExternals = require('webpack-node-externals')
2 | const webpack = require('webpack')
3 | const path = require('path')
4 |
5 | const config = {
6 | devtool: 'cheap-module-source-map',
7 | resolve: {
8 | extensions: ['.ts', '.js', '.html', '.scss', '.css']
9 | },
10 | resolveLoader: {
11 | moduleExtensions: ['-loader']
12 | },
13 | target: 'node',
14 | externals: [nodeExternals()],
15 | module: {
16 | rules: [
17 | {
18 | test: /\.ts$/,
19 | loaders: ['awesome-typescript-loader', 'angular2-template-loader']
20 | },
21 | {
22 | test: /\.gql$/,
23 | loader: 'graphql-tag/loader'
24 | },
25 | {
26 | test: /\.html$/,
27 | loader: 'raw-loader'
28 | },
29 | {
30 | test: /\.scss$/,
31 | use: [
32 | 'to-string-loader',
33 | 'css-loader',
34 | 'postcss-loader',
35 | 'sass-loader'
36 | ]
37 | },
38 | {
39 | test: /\.css$/,
40 | loader: 'null'
41 | },
42 | {
43 | test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
44 | loader: 'null'
45 | }
46 | ]
47 | }
48 | }
49 |
50 | module.exports = config
51 |
--------------------------------------------------------------------------------
/src/boostrap.ts:
--------------------------------------------------------------------------------
1 | import { UIGuideSandbox } from './interfaces'
2 | import { UIGuideModule } from './ui-guide.module'
3 | import { getModuleForUIGuides } from './generate-hosts'
4 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
5 | import { provideUIGuides } from './services/ui-guide.service'
6 | import { provideResolvedSandbox } from './services/sandbox.service'
7 | import { NgModuleRef } from '@angular/core'
8 | import { ENTRY_MARKDOWN } from './services'
9 |
10 | /** boostrap the UI guide app */
11 | export function bootstrap(sandBox: UIGuideSandbox): Promise> {
12 | /** grab all the ui guides */
13 | const uiGuides = sandBox.loadUIGuides()
14 | /** resolve all the ui guide components and ng module */
15 | const resolved = getModuleForUIGuides(sandBox.ngModule, uiGuides, sandBox.ngModuleImports)
16 | /** this is the markdown used in your index page */
17 | const entryMarkdown = sandBox.entryMarkdown
18 |
19 | /** add in the extra providers to the app */
20 | const platform = platformBrowserDynamic([
21 | provideUIGuides(uiGuides),
22 | provideResolvedSandbox(resolved),
23 | {provide: ENTRY_MARKDOWN, useValue: entryMarkdown}
24 | ])
25 |
26 | /** boostrap the app */
27 | return platform.bootstrapModule(UIGuideModule)
28 | }
29 |
--------------------------------------------------------------------------------
/src/ui-guide.module.ts:
--------------------------------------------------------------------------------
1 | import {NgModule} from '@angular/core'
2 | import {BrowserModule} from '@angular/platform-browser'
3 | import { CommonModule } from '@angular/common'
4 | import { UIGuideSandboxService, UIGuideSerivce } from './services'
5 | import { UIExampleComponent, UIGuideComponent, UIGudiesListComponent, UIApiComponent, PrismComponent, MarkdownComponent } from './components'
6 | import { ComponentsView, UIGuidePreviewView, UIGuideRootView, UIGuideRouterEntryView } from './views'
7 | import { Routing } from './routes'
8 | import 'prismjs/prism';
9 | import 'prismjs/components/prism-markup';
10 | import 'prismjs/components/prism-typescript';
11 |
12 | @NgModule({
13 | imports: [
14 | BrowserModule,
15 | CommonModule,
16 | Routing
17 | ],
18 | providers: [
19 | UIGuideSandboxService,
20 | UIGuideSerivce
21 | ],
22 | declarations: [
23 | PrismComponent,
24 | UIGudiesListComponent,
25 | UIExampleComponent,
26 | UIGuideComponent,
27 | UIGuideRouterEntryView,
28 | UIGuideRootView,
29 | UIGuidePreviewView,
30 | ComponentsView,
31 | UIApiComponent,
32 | MarkdownComponent
33 | ],
34 | entryComponents: [
35 | UIGuidePreviewView,
36 | UIGuideRouterEntryView
37 | ],
38 | bootstrap: [UIGuideRootView]
39 | })
40 | export class UIGuideModule {}
41 |
--------------------------------------------------------------------------------
/src/styles.ts:
--------------------------------------------------------------------------------
1 | export const constants = {
2 | maxZ: 9999999
3 | }
4 |
5 | export const sizes = {
6 | sidbarWidth: '15em',
7 | fullHeight: '100vh',
8 | navbarHeight: '20px',
9 | mobileNavHeight: '275px'
10 | }
11 |
12 | export const colors = {
13 | white: '#FAFAFA',
14 | main: '#E91E63',
15 | mainDark: '#C2185B',
16 | accent: '#AB47BC',
17 | accentDark: '#6A1B9A'
18 | }
19 |
20 | export const fonts = {
21 | sizes: {
22 | small: '.8rem',
23 | regular: '1rem',
24 | large: '1.5rem',
25 | xlarge: '2rem'
26 | },
27 | thickness: {
28 | lightest: 100,
29 | light: 300,
30 | regular: 500,
31 | bold: 700
32 | }
33 | }
34 |
35 | export const media = {
36 | greaterThanPhone(styles) {
37 | return `@media only screen and (min-width: 620px){
38 | ${styles}
39 | }`
40 | },
41 | tablet(styles) {
42 | return `@media only screen and (min-width: 620px) and (max-width: 1024px) {
43 | ${styles}
44 | }`
45 | },
46 | desktop(styles){
47 | return `@media only screen and (min-width: 1025px) and (max-width: 1200px) {
48 | ${styles}
49 | }`
50 | },
51 | monitor(styles) {
52 | return `@media only screen and (min-width: 1201px) {
53 | ${styles}
54 | }`
55 | },
56 | phone(styles) {
57 | return `@media only screen and (max-width: 619px) {
58 | ${styles}
59 | }`
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/services/sandbox.service.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Injectable,
3 | NgModuleFactory,
4 | OpaqueToken,
5 | Inject,
6 | Compiler,
7 | Injector
8 | } from '@angular/core'
9 | import { ResolvedUIGuideSandbox, CompiledUIGuide } from '../interfaces'
10 |
11 | export const RESOLVED_UI_GUIDE_SANDBOX = new OpaqueToken('Resolved ui guide sandbox')
12 |
13 |
14 | @Injectable()
15 | export class UIGuideSandboxService {
16 | private uiGuideSandbox: ResolvedUIGuideSandbox
17 | private factory: NgModuleFactory
18 |
19 | constructor(
20 | @Inject(RESOLVED_UI_GUIDE_SANDBOX) sandbox: ResolvedUIGuideSandbox,
21 | compiler: Compiler
22 | ) {
23 | this.uiGuideSandbox = sandbox
24 | /** create the factory for the ui guides ngModule */
25 | this.factory = compiler.compileModuleSync(this.uiGuideSandbox.ngModule)
26 | }
27 |
28 | /** compile component used in an ui guide */
29 | compilerUIGuide(id: string, injector: Injector ): CompiledUIGuide {
30 | const component = this.uiGuideSandbox.components[id]
31 | const ref = this.factory.create(injector)
32 | const factory = ref.componentFactoryResolver.resolveComponentFactory(component)
33 |
34 | return {factory, injector: ref.injector}
35 | }
36 | }
37 |
38 | export function provideResolvedSandbox(sandbox: ResolvedUIGuideSandbox) {
39 | return { provide: RESOLVED_UI_GUIDE_SANDBOX, useValue: sandbox }
40 | }
41 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": ["node_modules/tslint-no-unused-var"],
3 | "rules": {
4 | "max-line-length": [true, 100],
5 | "class-name": true,
6 | "comment-format": [
7 | true,
8 | "check-space"
9 | ],
10 | "indent": [
11 | true,
12 | "spaces"
13 | ],
14 | "eofline": true,
15 | "no-duplicate-variable": true,
16 | "no-eval": true,
17 | "no-arg": true,
18 | "no-internal-module": true,
19 | "no-trailing-whitespace": true,
20 | "no-bitwise": true,
21 | "no-shadowed-variable": true,
22 | "no-unused-expression": true,
23 | "no-unused-var": [true, {"ignore-pattern": "^(_.*)$"}],
24 | "one-line": [
25 | true,
26 | "check-catch",
27 | "check-else",
28 | "check-open-brace",
29 | "check-whitespace"
30 | ],
31 | "quotemark": [
32 | true,
33 | "single",
34 | "avoid-escape"
35 | ],
36 | // "semicolon": false,
37 | "typedef-whitespace": [
38 | true,
39 | {
40 | "call-signature": "nospace",
41 | "index-signature": "nospace",
42 | "parameter": "nospace",
43 | "property-declaration": "nospace",
44 | "variable-declaration": "nospace"
45 | }
46 | ],
47 | "curly": true,
48 | "variable-name": [
49 | true,
50 | "ban-keywords",
51 | "check-format",
52 | "allow-leading-underscore"
53 | ],
54 | "whitespace": [
55 | true,
56 | "check-branch",
57 | "check-decl",
58 | "check-operator",
59 | "check-separator",
60 | "check-type"
61 | ]
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/cli.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import * as program from 'commander'
3 | import * as path from 'path'
4 | import * as chalk from 'chalk'
5 | import { startServer } from './webpack/serve'
6 | import { build } from './webpack/build'
7 | const pjson = require('../package.json')
8 |
9 | const getConfig = (config) => {
10 | let configPath = config.config || './dope'
11 | const parsed = path.parse(configPath)
12 | configPath = path.join(parsed.dir, parsed.name)
13 |
14 | return configPath
15 | }
16 |
17 | const serve = (config) => {
18 | try {
19 | const configFile = require(path.join(process.cwd(), getConfig(config)))
20 | startServer(configFile)
21 | } catch (e) {
22 | console.log(chalk.red(e.message))
23 | console.log(chalk.red('dope config file not found. Make sure you have a dope.js'))
24 | process.exit(1)
25 | }
26 | }
27 |
28 | const buildDocs = (config) => {
29 | try {
30 | const configFile = require(path.join(process.cwd(), getConfig(config)))
31 | build(configFile)
32 | } catch (e) {
33 | console.log(chalk.red(e.message))
34 | console.log(chalk.red('dope config file not found. Make sure you have a dope.js'))
35 | process.exit(1)
36 | }
37 | }
38 |
39 |
40 | program.version(pjson.version)
41 |
42 | program
43 | .command('serve')
44 | .option('-c, --config [configFilePath]', 'path to config file. Defaults to ./dope.js')
45 | .description('build and serve your Dope Docs')
46 | .action(serve)
47 |
48 | program
49 | .command('build')
50 | .option('-c, --config [configFilePath]', 'path to config file. Defaults to ./dope.js')
51 | .description('build your Dope Docs')
52 | .action(buildDocs)
53 |
54 | program
55 | .command('*')
56 | .action(buildDocs)
57 |
58 | program.parse(process.argv)
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/services/ui-guide.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, Inject, OpaqueToken } from '@angular/core'
2 | import { UIGuide, UIGuideExample, ResolvedUIGuideSandbox } from '../interfaces'
3 |
4 | export const UI_GUIDES = new OpaqueToken('UIGuides')
5 | /** type for id:value object */
6 | export type IdMap = {[id: string]: T}
7 |
8 | @Injectable()
9 | export class UIGuideSerivce {
10 | /** collection of all the ui guides created */
11 | private uiGuides: IdMap = {}
12 | /**collection of all the ui guide examples */
13 | private uiGuideExamples: IdMap = {}
14 |
15 | constructor(@Inject(UI_GUIDES) uiGuides: UIGuide[]) {
16 | /** turn guies into a map */
17 | this.uiGuides = uiGuides.reduce>(this.byId, {})
18 | /** turn examples into a map */
19 | this.uiGuideExamples = uiGuides.reduce>((all, next) => {
20 | const examples = next.examples.reduce>(this.byId, {})
21 | return Object.assign({}, all, examples)
22 | }, {})
23 | }
24 |
25 | /** get one ui guide for id */
26 | getUIGuide(id: string): UIGuide {
27 | return this.uiGuides[id]
28 | }
29 |
30 | /** get one example for ID */
31 | getUIGuideExample(id: string): UIGuideExample {
32 | return this.uiGuideExamples[id]
33 | }
34 |
35 | /** get all ui guides */
36 | getAllUIGuides(): UIGuide[] {
37 | return Object.keys(this.uiGuides)
38 | .map(key => this.uiGuides[key])
39 | }
40 |
41 | private byId(all: IdMap = {}, next: T): IdMap {
42 | return Object.assign({}, all, {[next.id]: next})
43 | }
44 | }
45 |
46 | export function provideUIGuides(uiGuides: UIGuide[]) {
47 | return {provide: UI_GUIDES, useValue: uiGuides}
48 | }
49 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack')
2 | const path = require('path')
3 | const root = (_path) => {
4 | return path.resolve(__dirname, _path)
5 | }
6 | const moduleExternals = require('webpack-node-externals')
7 |
8 | module.exports = (envOptions = {}) => {
9 | return ({
10 | entry: {
11 | 'dope-docs': root('./src/index.ts')
12 | },
13 | output: {
14 | path: root('dist'),
15 | filename: '[name].js'
16 | },
17 | target: 'web',
18 | externals: [moduleExternals()],
19 | resolve: {
20 | extensions: ['.ts', '.js', '.html', '.scss', '.css'],
21 | },
22 | module: {
23 | rules: [
24 | {
25 | test: /\.ts$/,
26 | loaders: [
27 | {loader: 'shebang-loader'},
28 | {
29 | loader: 'awesome-typescript-loader',
30 | options: {
31 | declaration: false
32 | }
33 | },
34 | 'angular2-template-loader'
35 | ],
36 | exclude: [/node_modules/, /\.spec\.ts$/]
37 | },
38 | {
39 | test: /\.html$/,
40 | loader: 'raw-loader'
41 | },
42 | {
43 | test: /\.gql$/,
44 | loader: 'graphql-tag/loader'
45 | },
46 | {
47 | test: /\.scss$/,
48 | use: [
49 | 'to-string-loader',
50 | 'css-loader',
51 | 'postcss-loader',
52 | 'sass-loader'
53 | ]
54 | }
55 | ]
56 | },
57 | plugins: [
58 | new webpack.ContextReplacementPlugin(
59 | /angular(\\|\/)core(\\|\/)@angular/,
60 | root('src'), // location of your src
61 | {
62 | // your Angular Async Route paths relative to this root directory
63 | }
64 | )
65 | ]
66 | })
67 | }
68 |
--------------------------------------------------------------------------------
/src/generate-hosts.ts:
--------------------------------------------------------------------------------
1 | import { ModuleWithProviders, Type, Component, NgModule } from '@angular/core'
2 | import { UIGuide, ResolvedUIGuideSandbox, UIGuideExample } from './interfaces'
3 | const flatten = require('lodash.flatten')
4 |
5 | export function getModuleForUIGuides(
6 | givenModule: Type,
7 | uiGuides: UIGuide[],
8 | ngModuleImports: Type[] = []
9 | ): ResolvedUIGuideSandbox {
10 |
11 | const componentsWithIds: {id: string, component: Type}[] = flatten(uiGuides.map((uiGuide => uiGuide.examples.map((ex => {
12 | return {
13 | id: ex.id,
14 | component: generateComponent(ex)
15 | }
16 | })))))
17 |
18 | console.log('components with ids', componentsWithIds)
19 |
20 | const components = componentsWithIds.reduce((all, next) => {
21 | return Object.assign({}, all, {
22 | [next.id]: next.component
23 | })
24 | }, {})
25 |
26 | ngModuleImports.push(givenModule)
27 | const ngModule = generateNgModule(ngModuleImports, componentsWithIds.map(e => e.component))
28 | return {ngModule, components}
29 | }
30 |
31 | export function generateComponent(
32 | example: UIGuideExample
33 | ): Type {
34 | @Component({
35 | template: example.template,
36 | styles: example.styles,
37 | providers: example.providers || []
38 | })
39 | class UIGuideExampleComponent {
40 | constructor() {
41 | Object.assign(this, example.context || {})
42 | }
43 | }
44 |
45 | return UIGuideExampleComponent
46 | }
47 |
48 | export function generateNgModule(
49 | givenModules: Type[],
50 | components: Type[]
51 | ): Type {
52 | @NgModule({
53 | imports: givenModules,
54 | declarations: [
55 | ...components
56 | ],
57 | entryComponents: [
58 | ...components
59 | ]
60 | })
61 | class UIGuideNgModule {}
62 |
63 | return UIGuideNgModule
64 | }
65 |
--------------------------------------------------------------------------------
/src/components/ui-guide.component.ts:
--------------------------------------------------------------------------------
1 | import { UIGuideSandboxService, UIGuideSerivce } from '../services'
2 | import { UIGuideExample } from '../interfaces'
3 | import { UIGuide } from '../interfaces'
4 | import {
5 | Component,
6 | ComponentRef,
7 | Injector,
8 | Input,
9 | OnDestroy,
10 | TemplateRef,
11 | ViewChild,
12 | ViewContainerRef
13 | } from '@angular/core'
14 |
15 | @Component({
16 | selector: 'ui-guide',
17 | template: `
18 |
19 |
20 |
21 |
{{guide.name}}
22 |
23 |
24 |
{{guide.description}}
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | `,
35 | styles: [`
36 | .guide {
37 | margin: 0px !important;
38 | padding: 2rem 3.5rem;
39 | }
40 | .example, .guide-text {
41 | margin-bottom: 5rem;
42 | }
43 | .title, .api {
44 | margin-bottom: 2rem;
45 | }
46 | .title {
47 | font-size: 3.5rem;
48 | font-weight: lighter;
49 | }
50 | .guide-description {
51 | font-size: 1.5rem;
52 | }
53 | `]
54 | })
55 | export class UIGuideComponent {
56 | guide: UIGuide = {id: '', name: '', examples: [], description: '', api: {inputs: [], outputs: []}}
57 |
58 | constructor(
59 | private sandboxService: UIGuideSandboxService,
60 | private uiGuideService: UIGuideSerivce
61 | ) {}
62 |
63 |
64 | @Input() set guideId(id: string) {
65 | if (!id || typeof id !== 'string') {
66 | return
67 | }
68 | this.guide = this.uiGuideService.getUIGuide(id)
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/ui-guide-builder.ts:
--------------------------------------------------------------------------------
1 | import { UIGuideSandbox, UIGuide, UIGuideExample, UIGuideExampleConfig, ComponentAPI, DopeDoc } from './interfaces'
2 | import { bootstrap } from './boostrap'
3 | const slugify = require('slugify')
4 |
5 | export function createDopeDocs(sandbox: UIGuideSandbox) {
6 | bootstrap(sandbox)
7 | }
8 |
9 | const GuideIDS: {[id: string]: string[]} = {}
10 |
11 | export class DocsBuilder implements DopeDoc {
12 | id: string
13 | examples: UIGuideExample[] = []
14 |
15 | constructor(public name: string, public description: string, public api?: ComponentAPI) {
16 | const id = slugify(name).toLowerCase()
17 |
18 | if (GuideIDS[id]) {
19 | throw new Error(`You already have a UI Guide with the name of ${name}`)
20 | }
21 |
22 | GuideIDS[id] = []
23 | this.id = id
24 | }
25 |
26 | example(name: string, config: UIGuideExampleConfig): DopeDoc {
27 | const id = `${this.id}-${slugify(name).toLowerCase()}`
28 |
29 | if (GuideIDS[this.id].find(i => i === id)) {
30 | throw new Error(`You already have an example for UI Guide ${this.name} with the name of ${name}`)
31 | }
32 |
33 | GuideIDS[this.id].push(id)
34 |
35 | this.examples.push({
36 | id,
37 | name,
38 | providers: config.providers || [],
39 | showSource: Boolean(config.showSource),
40 | uiGuideName: this.name,
41 | description: config.description,
42 | template: config.template,
43 | context: config.context,
44 | styles: config.styles
45 | })
46 |
47 | return this
48 | }
49 |
50 | xexample(description: string, config: UIGuideExampleConfig): DopeDoc {
51 | return this
52 | }
53 |
54 | Xexample(description: string, config: UIGuideExampleConfig): DopeDoc {
55 | return this.xexample(description, config)
56 | }
57 | }
58 |
59 | export function docsFor(
60 | component: string,
61 | description: string,
62 | api: ComponentAPI = {inputs: [], outputs: []}
63 | ): DopeDoc {
64 | return new DocsBuilder(component, description, api)
65 | }
66 |
--------------------------------------------------------------------------------
/src/webpack/serve.ts:
--------------------------------------------------------------------------------
1 | import * as webpack from 'webpack'
2 | import * as WebpackDevServer from 'webpack-dev-server'
3 | import * as path from 'path'
4 | import { UIGuideBuildConfig, DevServerConfig } from '../interfaces'
5 | import { Configuration } from 'webpack'
6 |
7 | const HTMLPlugin = require('html-webpack-plugin')
8 | const ProgressPlugin = require('webpack/lib/ProgressPlugin')
9 | const chalk = require('chalk')
10 |
11 | export function startServer(config: UIGuideBuildConfig) {
12 | const webpackConfig = config.webpackConfig
13 | const serverConfig: DevServerConfig = webpackConfig.devServer
14 |
15 | let host = config.host || 'localhost'
16 | let port = config.port || 4000
17 |
18 | if (serverConfig) {
19 | host = serverConfig.host || host
20 | port = serverConfig.port || port
21 | }
22 |
23 | webpackConfig.entry = {
24 | main: [
25 | `webpack-dev-server/client?http://${host}:${port}/`,
26 | config.entry
27 | ]
28 | }
29 |
30 | webpackConfig.plugins = webpackConfig.plugins.filter(p => !(p instanceof HTMLPlugin))
31 |
32 | const compiler = webpack(webpackConfig)
33 |
34 | compiler.apply(new ProgressPlugin({
35 | colors: true,
36 | profile: true
37 | }))
38 |
39 | compiler.apply(new HTMLPlugin({
40 | template: path.resolve(__dirname, '../../index.html')
41 | }))
42 |
43 | const devServerConfig = Object.assign({}, {
44 | historyApiFallback: true,
45 | stats: {
46 | assets: true,
47 | colors: true,
48 | version: true,
49 | hash: true,
50 | timings: true,
51 | chunks: false,
52 | chunkModules: false
53 | },
54 | inline: true,
55 |
56 | }, serverConfig)
57 |
58 |
59 | const server = new WebpackDevServer(compiler, devServerConfig)
60 |
61 | return new Promise((resolve, reject) => {
62 | server.listen(port, `${host}`, (err, stats) => {
63 | if (err) {
64 | console.error(err.stack || err)
65 | if (err.details) {
66 | console.error(err.details)
67 | reject(err.details)
68 | }
69 | }
70 | })
71 | })
72 | }
73 |
--------------------------------------------------------------------------------
/src/components/ui-api.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input } from '@angular/core'
2 | import { ComponentAPI } from '../interfaces'
3 | import { colors, fonts } from '../styles'
4 |
5 |
6 | @Component({
7 | selector: 'ui-api',
8 | template: `
9 |
10 |
32 |
33 |
Outputs
34 |
35 |
36 | {{outputLabel}}
37 |
38 |
39 |
40 |
41 | {{output.name}}
42 |
43 |
44 | {{output.description}}
45 |
46 |
47 | {{output.args}}
48 |
49 |
50 |
51 |
52 | `,
53 | styles: [`
54 | .title {
55 | font-size: ${fonts.sizes.xlarge};
56 | font-weight: ${fonts.thickness.light};
57 | margin-bottom: 3rem;
58 | }
59 | .labels {
60 | border-bottom: .5px solid ${colors.accentDark};
61 | margin-bottom: 2rem;
62 | }
63 | .label {
64 | color: ${colors.accent};
65 | font-weight: ${fonts.thickness.light};
66 | font-size: ${fonts.sizes.large};
67 | }
68 | .values {
69 | margin-bottom: 1.5rem;
70 | font-weight: ${fonts.thickness.lightest};
71 | font-size: ${fonts.sizes.regular};
72 | }
73 | `]
74 | })
75 | export class UIApiComponent {
76 | inputLabels = ['name', 'description', 'type', 'default']
77 | outputLabels = ['name', 'description', 'args']
78 |
79 | @Input() api: ComponentAPI = {inputs: [], outputs: []}
80 |
81 | getColForOutputs(outputLabel: string) {
82 | return {
83 | name: 'col-2',
84 | description: 'col-5',
85 | args: 'col-5'
86 | }[outputLabel]
87 | }
88 |
89 | getColForInputs(inputLabel: string) {
90 | return {
91 | name: 'col-2',
92 | description: 'col-5',
93 | type: 'col-2',
94 | default: 'col-3'
95 | }[inputLabel]
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/interfaces.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Injector,
3 | ComponentFactory,
4 | Type,
5 | ModuleWithProviders
6 | } from '@angular/core'
7 |
8 | import {Configuration} from 'webpack'
9 |
10 | export interface UIGuideExample {
11 | id: string
12 | description: string
13 | name: string
14 | uiGuideName: string
15 | template: string
16 | showSource?: boolean
17 | styles?: string[]
18 | context?: any
19 | providers?: any[]
20 | }
21 |
22 | export interface UIGuide {
23 | id: string
24 | name: string
25 | description: string
26 | examples: UIGuideExample[]
27 | api?: ComponentAPI
28 | imports?: any[]
29 | declarations?: any[]
30 | providers?: any[]
31 | declareComponent?: boolean
32 | }
33 |
34 | export interface CompiledUIGuide {
35 | injector: Injector
36 | factory: ComponentFactory
37 | }
38 |
39 | export interface UIGuideSandbox {
40 | entryMarkdown: string
41 | ngModule: Type
42 | ngModuleImports?: Type[]
43 | loadUIGuides(): UIGuide[]
44 | };
45 |
46 | export interface ResolvedUIGuideSandbox {
47 | ngModule: Type
48 | components: { [id: string]: Type }
49 | }
50 |
51 | export interface UIGuideExampleConfig {
52 | template: string
53 | description: string
54 | context?: any
55 | showSource?: boolean
56 | styles?: string[]
57 | providers?: any[]
58 | }
59 |
60 | export interface UIGuideBuildConfig {
61 | webpackConfig: Configuration
62 | entry: string
63 | host?: string
64 | port?: number
65 | include?: string[]
66 | }
67 |
68 | export interface DevServerConfig {
69 | port?: number
70 | proxy?: any
71 | host?: string
72 | quiet?: boolean
73 | noInfo?: boolean
74 | watchOptions?: any
75 | https?: boolean
76 | publicPath: string
77 | }
78 |
79 | export interface ComponentInput {
80 | /** the name of the @Inpout */
81 | name: string
82 | /** the value type of the input */
83 | type: 'string'|'object'|'number'|'boolean'|'array'
84 | /** input description */
85 | description: string
86 | /** the default value of the input if any */
87 | default?: string
88 | }
89 |
90 | export interface ComponentOuput {
91 | /** the name of the output */
92 | name: string
93 | /** describe when this event is fired */
94 | description: string
95 | /** args passed through output if any */
96 | args?: string
97 | }
98 |
99 | export interface ComponentAPI {
100 | inputs?: ComponentInput[]
101 | outputs?: ComponentOuput[]
102 | }
103 |
104 | export interface DopeDoc extends UIGuide {
105 | example(name: string, config: UIGuideExampleConfig): DopeDoc
106 | xexample(name?: string, config?: UIGuideExampleConfig): DopeDoc
107 | Xexample(name?: string, config?: UIGuideExampleConfig): DopeDoc
108 | }
109 |
110 | export declare var DocsBuilder: {
111 | new(name: string, description: string, api?: ComponentAPI): DopeDoc
112 | example(name: string, config: UIGuideExampleConfig): DopeDoc
113 | xexample(name?: string, config?: UIGuideExampleConfig): DopeDoc
114 | Xexample(name?: string, config?: UIGuideExampleConfig): DopeDoc
115 | }
116 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@angularclass/dope-docs",
3 | "version": "0.9.9",
4 | "description": "Document and styleguide generator for your Angular projects",
5 | "main": "dope-docs.js",
6 | "jsnext:main": "index.js",
7 | "module": "index.js",
8 | "types": "index.d.ts",
9 | "scripts": {
10 | "test": "rimraf ./tmp/mocha-webpack && mocha-webpack --opts 'mocha.opts'",
11 | "build:esm": "ntsc -p tsconfig.esm.json",
12 | "build:cli": "ntsc -p tsconfig.cli.json",
13 | "build:bundle": "webpack --progress --colors",
14 | "build:aot": "ngc",
15 | "build": "rimraf build && npm run build:esm && npm run build:bundle && npm run build:aot",
16 | "copy": "cp package.json dist && cp README.md dist && cp src/webpack/index.html dist/index.html && cp -R src/assets dist",
17 | "package": "rm -rf build && npm run build:esm && npm run build:bundle && npm run build:cli && npm run copy",
18 | "tags": "git add . && git push origin master && git push --tags",
19 | "bump": "npm version patch -m\"upgrade to %s\"",
20 | "release": "npm run bump && npm run tags && npm run package && npm publish dist/ --access public"
21 | },
22 | "bugs": "https://github.com/AngularClass/dope-docs/issues",
23 | "repository": {
24 | "url": "https://github.com/AngularClass/dope-docs",
25 | "type": "git"
26 | },
27 | "author": "Scott Moss ",
28 | "bin": {
29 | "acdocs": "./cli/cli.js"
30 | },
31 | "license": "MIT",
32 | "devDependencies": {
33 | "@angular/common": "^4.1.3",
34 | "@angular/compiler": "^4.1.3",
35 | "@angular/compiler-cli": "^4.1.3",
36 | "@angular/core": "^4.1.3",
37 | "@angular/platform-browser": "^4.1.3",
38 | "@angular/platform-browser-dynamic": "^4.1.3",
39 | "@angular/platform-server": "^4.1.3",
40 | "@types/chai": "^3.5.2",
41 | "@types/mocha": "^2.2.41",
42 | "@types/node": "^7.0.18",
43 | "@types/sinon": "^2.2.2",
44 | "@types/webpack": "^2.2.15",
45 | "@types/webpack-dev-server": "^2.4.0",
46 | "angular2-template-loader": "^0.6.2",
47 | "awesome-typescript-loader": "^3.1.3",
48 | "core-js": "^2.4.1",
49 | "html-webpack-plugin": "^2.28.0",
50 | "jsdom": "^10.1.0",
51 | "mocha": "^3.4.1",
52 | "mocha-webpack": "^0.7.0",
53 | "ntypescript": "latest",
54 | "raw-loader": "^0.5.1",
55 | "rimraf": "^2.6.1",
56 | "rxjs": "^5.4.0",
57 | "source-map-support": "^0.4.15",
58 | "typescript": "^2.3.2",
59 | "webpack": "^2.6.0",
60 | "webpack-node-externals": "^1.6.0",
61 | "zone.js": "^0.8.10"
62 | },
63 | "dependencies": {
64 | "@angular/animations": "^4.1.3",
65 | "@angular/forms": "^4.1.3",
66 | "@angular/router": "^4.1.3",
67 | "@types/chalk": "^0.4.31",
68 | "@types/commander": "^2.9.0",
69 | "@types/html-webpack-plugin": "^2.28.0",
70 | "@types/marked": "^0.0.28",
71 | "angular-prism": "^0.1.20",
72 | "angular2-highlight-js": "^5.0.0",
73 | "chalk": "^1.1.3",
74 | "commander": "^2.9.0",
75 | "css-loader": "^0.28.1",
76 | "lodash.flatten": "^4.4.0",
77 | "marked": "^0.3.6",
78 | "postcss-loader": "^2.0.5",
79 | "prismjs": "^1.6.0",
80 | "sass-loader": "^6.0.5",
81 | "shebang-loader": "^0.0.1",
82 | "slugify": "^1.1.0",
83 | "to-string-loader": "^1.1.5",
84 | "webpack-dev-server": "^2.4.5"
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/components/ui-example.component.ts:
--------------------------------------------------------------------------------
1 | import { UIGuideSandboxService, UIGuideSerivce } from '../services'
2 | import { UIGuideExample } from '../interfaces'
3 | import { sizes, fonts } from '../styles'
4 | import {
5 | Component,
6 | ComponentRef,
7 | Injector,
8 | Input,
9 | OnDestroy,
10 | TemplateRef,
11 | ViewChild,
12 | ViewContainerRef
13 | } from '@angular/core'
14 |
15 | @Component({
16 | selector: 'ui-example',
17 | template: `
18 |
19 |
20 |
{{example.name}}
21 |
22 |
23 |
{{example.description}}
24 |
25 |
28 |
39 |
40 | `,
41 | styles: [`
42 | .example {
43 | flex-direction: column;
44 | }
45 | .title {
46 | margin-bottom: 3rem;
47 | }
48 | .title * {
49 | font-size: ${fonts.sizes.large};
50 | font-weight: ${fonts.thickness.light};
51 | }
52 | .description {
53 | margin-bottom: 3rem;
54 | }
55 | .description p {
56 | font-size: ${fonts.sizes.regular};
57 | font-weight: 100;
58 | }
59 | .source {
60 | font-size: ${fonts.sizes.regular};
61 | }
62 | .example-component {
63 | margin-bottom: 3rem;
64 | }
65 | `]
66 | })
67 | export class UIExampleComponent implements OnDestroy {
68 | example: UIGuideExample = {id: '', description: '', name: '', uiGuideName: '', template: ''}
69 | private ref: ComponentRef
70 |
71 | @ViewChild('uiGuideExample', {read: ViewContainerRef})
72 | public exampleConatiner: ViewContainerRef
73 |
74 | constructor(
75 | private sandboxService: UIGuideSandboxService,
76 | private uiGuideService: UIGuideSerivce,
77 | private injector: Injector
78 | ) {}
79 |
80 | private refresh() {
81 | if (this.ref) {
82 | this.ref.destroy()
83 | this.ref = null
84 | }
85 | }
86 |
87 | @Input() set guideExample(example: UIGuideExample) {
88 | if (!example) {
89 | return
90 | }
91 |
92 | this.refresh()
93 | this.example = example
94 | const {factory, injector} = this.sandboxService.compilerUIGuide(example.id, this.injector)
95 | this.ref = this.exampleConatiner.createComponent(factory, 0, injector, [])
96 | }
97 |
98 | generateClassCode(context: any) {
99 | const contextString = Object.keys(context).reduce((final, next) => {
100 | let value = context[next]
101 | if (value) {
102 | value = value.toString()
103 | }
104 | return final += ` ${next} = ${value}\n`
105 | }, '\n')
106 |
107 | return `/* host component class */\nclass HostComponent {${contextString}}`
108 | }
109 |
110 | generateTemplateCode(template: string) {
111 | const templateList = template.split('\n')
112 | const baseSpaceCount = templateList[0].search(/\S/)
113 |
114 | const formattedTemplate = templateList.reduce((final, next) => {
115 | const spaces = next.search(/\S/)
116 | const diff = baseSpaceCount - spaces
117 | let s = next.trim()
118 |
119 | if (diff && diff > 0) {
120 | s = new Array(diff + 1) + s
121 | }
122 | final += s + '\n'
123 | return final
124 | }, '\n')
125 | return `\n${template}`
126 | }
127 |
128 |
129 | ngOnDestroy() {
130 | this.refresh()
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/views/components.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Inject, ViewEncapsulation } from '@angular/core'
2 | import { ENTRY_MARKDOWN } from '../services'
3 | @Component({
4 | selector: 'ui-guide-components-view',
5 | template: `
6 |
7 |
8 |
9 | `,
10 | encapsulation: ViewEncapsulation.None,
11 | styles: [`
12 | @media print {
13 | *,
14 | *:before,
15 | *:after {
16 | background: transparent !important;
17 | color: #000 !important;
18 | box-shadow: none !important;
19 | text-shadow: none !important;
20 | }
21 |
22 | .entry-component a,
23 | .entry-component a:visited {
24 | text-decoration: underline;
25 | }
26 |
27 | .entry-component a[href]:after {
28 | content: " (" attr(href) ")";
29 | }
30 |
31 | .entry-component abbr[title]:after {
32 | content: " (" attr(title) ")";
33 | }
34 |
35 | .entry-component a[href^="#"]:after,
36 | .entry-component a[href^="javascript:"]:after {
37 | content: "";
38 | }
39 |
40 | .entry-component pre,
41 | .entry-component blockquote {
42 | border: 1px solid #999;
43 | page-break-inside: avoid;
44 | }
45 |
46 | .entry-component thead {
47 | display: table-header-group;
48 | }
49 |
50 | .entry-component tr,
51 | .entry-component img {
52 | page-break-inside: avoid;
53 | }
54 |
55 | .entry-component img {
56 | max-width: 100% !important;
57 | }
58 |
59 | .entry-component p,
60 | .entry-component h2,
61 | .entry-component h3 {
62 | orphans: 3;
63 | widows: 3;
64 | }
65 |
66 | .entry-component h2,
67 | .entry-component h3 {
68 | page-break-after: avoid;
69 | }
70 | }
71 |
72 | .entry-component {
73 | line-height: 1.85;
74 | }
75 |
76 | .entry-component p {
77 | font-size: 1rem;
78 | margin-bottom: 1.3rem;
79 | }
80 |
81 | .entry-component h1,
82 | .entry-component h2,
83 | .entry-component h3,
84 | .entry-component h4 {
85 | margin: 1.414rem 0 .5rem;
86 | font-weight: inherit;
87 | line-height: 1.42;
88 | }
89 |
90 | .entry-component h1 {
91 | margin-top: 0;
92 | font-size: 3.998rem;
93 | }
94 |
95 | .entry-component h2 {
96 | font-size: 2.827rem;
97 | }
98 |
99 | .entry-component h3 {
100 | font-size: 1.999rem;
101 | }
102 |
103 | .entry-component h4 {
104 | font-size: 1.414rem;
105 | }
106 |
107 | .entry-component h5 {
108 | font-size: 1.121rem;
109 | }
110 |
111 | .entry-component h6 {
112 | font-size: .88rem;
113 | }
114 |
115 | .entry-component small {
116 | font-size: .707em;
117 | }
118 |
119 | /* https://github.com/mrmrs/fluidity */
120 |
121 | .entry-component img,
122 | .entry-component canvas,
123 | .entry-component iframe,
124 | .entry-component video,
125 | .entry-component svg,
126 | .entry-component select,
127 | .entry-component textarea {
128 | max-width: 100%;
129 | }
130 |
131 | .entry-component {
132 | color: #444;
133 | font-family: 'Open Sans', Helvetica, sans-serif;
134 | font-weight: 300;
135 | padding: 2.5rem;
136 | margin: 0px;
137 | }
138 |
139 | .entry-component img {
140 | border-radius: 50%;
141 | height: 200px;
142 | margin: 0 auto;
143 | width: 200px;
144 | }
145 |
146 | .entry-component a,
147 | .entry-component a:visited {
148 | color: #3498db;
149 | }
150 |
151 | .entry-component a:hover,
152 | .entry-component a:focus,
153 | .entry-component a:active {
154 | color: #2980b9;
155 | }
156 |
157 | .entry-component pre {
158 | background-color: #fafafa;
159 | padding: 1rem;
160 | text-align: left;
161 | }
162 |
163 | .entry-component blockquote {
164 | margin: 0;
165 | border-left: 5px solid #7a7a7a;
166 | font-style: italic;
167 | padding: 1.33em;
168 | text-align: left;
169 | }
170 |
171 | .entry-component ul,
172 | .entry-component ol,
173 | .entry-component li {
174 | text-align: left;
175 | }
176 |
177 | .entry-component p {
178 | color: #777;
179 | }
180 | `]
181 | })
182 | export class ComponentsView {
183 | constructor(@Inject(ENTRY_MARKDOWN) public entryMarkdown: string) {}
184 | }
185 |
--------------------------------------------------------------------------------
/src/components/guides-list.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, Renderer2, Inject } from '@angular/core'
2 | import { sizes, colors, fonts, media, constants } from '../styles'
3 | import { DOCUMENT } from '@angular/platform-browser'
4 | import {
5 | trigger,
6 | style,
7 | state,
8 | animate,
9 | transition
10 | } from '@angular/animations'
11 |
12 | @Component({
13 | selector: 'ui-guides-list',
14 | template: `
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
28 | {{uiGuide.name}}
29 |
30 |
31 |
32 |
33 |
34 |
35 | Components
36 |
37 |
43 | {{uiGuide.name}}
44 |
45 |
46 | `,
47 | styles: [`
48 | .navbar {
49 | height: ${sizes.navbarHeight};
50 | position: fixed;
51 | top: 0;
52 | left: 0;
53 | width: 100%;
54 | padding: 1rem;
55 | background-color: ${colors.main};
56 | color: ${colors.white};
57 | }
58 |
59 | .nav-content {
60 | position: fixed;
61 | top: 0;
62 | left: 0;
63 | width: ${sizes.mobileNavHeight};
64 | padding: 1rem 0px;
65 | height: 100%;
66 | overflow-y: scroll;
67 | background-color: ${colors.main};
68 | color: ${colors.white};
69 | z-index: ${constants.maxZ}
70 | }
71 | .overlay {
72 | background-color: rgba(0,0,0,0.7);
73 | position: fixed;
74 | top: 0; right: 0; bottom: 0; left: 0;
75 | z-index: ${constants.maxZ - 1};
76 | }
77 | .mobile-nav {}
78 | ${media.greaterThanPhone('.navbar{display: none; visibility: hidden}')}
79 |
80 | .link:active {
81 | border: 0px;
82 | outline: none;
83 | }
84 | .link {
85 | cursor: pointer;
86 | outline: none;
87 | border: 0px;
88 | }
89 | .ui-guides-list {
90 | height: ${sizes.fullHeight};
91 | overflow-y: scroll;
92 | background-color: ${colors.main};
93 | color: ${colors.white};
94 | padding: 1.4rem 0px;
95 | position: fixed;
96 | top: 0;
97 | left: 0;
98 | width: ${sizes.sidbarWidth};
99 | }
100 | ${media.phone('.ui-guides-list{display: none; visibility: hidden}')}
101 | .title {
102 | font-size: ${fonts.sizes.large};
103 | text-align: left;
104 | padding: .5rem 1rem;
105 | transition: background-color .1s ease-in;
106 | }
107 | .item {
108 | font-size: ${fonts.sizes.regular};
109 | font-weight: ${fonts.thickness.light};
110 | padding: .5rem 1rem;
111 | line-height: ${fonts.sizes.regular};
112 | width: 100%;
113 | text-align: left;
114 | }
115 | .item:hover, .item.active, .title:hover, .title.active {
116 | background-color: ${colors.mainDark};
117 | }
118 | `],
119 | // animations: [
120 | // trigger('slideTrigger', [
121 | // state('open', style({width: sizes.sidbarWidth})),
122 | // state('closed', style({width: '0px'})),
123 | // transition('closed => open', animate('200ms ease-in')),
124 | // transition('open => closed', animate('200ms 200ms ease-out'))
125 | // ])
126 | // ]
127 | })
128 | export class UIGudiesListComponent {
129 | showNav = false
130 | navState = 'closed'
131 | @Input() uiGuides = []
132 |
133 |
134 | constructor(
135 | public renderer: Renderer2,
136 | @Inject(DOCUMENT) public docuemnt: Document
137 | ) {}
138 |
139 | openNav() {
140 | this.renderer.addClass(this.docuemnt.body, 'nav-open')
141 | this.showNav = true
142 | // this.navState = 'open'
143 | }
144 |
145 | closeNav() {
146 | this.renderer.removeClass(this.docuemnt.body, 'nav-open')
147 | this.showNav = false
148 | // this.navState = 'closed'
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Dope Docs
2 | > :lipstick: :green_book: Storybook inspired Angular docs generator
3 |
4 |
5 |
6 | - [Dope Docs](#dope-docs)
7 | - [What is this?](#what-is-this)
8 | - [Getting Starting](#getting-starting)
9 | - [Installing](#installing)
10 | - [Creating docs](#creating-docs)
11 | - [Bootstrapping](#bootstrapping)
12 | - [Setup build](#setup-build)
13 | - [Inspiration](#inspiration)
14 | - [Contributing](#contributing)
15 |
16 |
17 |
18 | ## What is this?
19 | Dope Docs is a CLI and Library that will create beautiful documentation, styleguides, and demos for your Angular UI bits (components, directives, pipes). Perfect your component libs, and styleguides for your team.
20 |
21 | ## Getting Starting
22 | Dope docs supports Angular 2 and beyond only. Now, lets get your docs going so you can stop procrastinating :100:.
23 |
24 | ### Installing
25 |
26 | * `yarn add @angularclass/dope-docs`
27 |
28 | ### Creating docs
29 | For every UI element you want to add, you'll create a doc for it. We follow the convention of `[name].doc.ts`. And then for each doc, you'll add examples. Below we have a button, and it has multiple states, so we create an example for each state.
30 |
31 | ```typescript
32 | // button.doc.ts
33 | /* you have to import DopeDoc for now or TS will complain, even though you won't use it. */
34 | import { docsFor, DopeDoc } from '@angularclass/dope-docs'
35 |
36 | export default docsFor(
37 | // the title for the doc you're creating for the UI element. Unique to the dope docs app
38 | 'Dope Button',
39 | // The description for this doc
40 | 'This button is so fire, yo have to use it. If you want the monies, use this button',
41 | // any @Inputs and or @Outputs for the UI element
42 | {inputs: [], outputs: []}
43 | )
44 | .example('primary', { // the name of this example, unique to this doc
45 | // the description of this example
46 | description: 'Default and primary button state',
47 | // show the source code in the docs?
48 | showSource: true,
49 | // props to pass the the template to use for data binding
50 | context: {
51 | type: 'primary'
52 | },
53 | // the template to render in the docs. Make sure it compliments the example name and description. Don't mislead people!
54 | template: `
55 |
56 | click me
57 |
58 | `
59 | })
60 | .example('warning', {
61 | template: `
62 |
63 | click me
64 |
65 | `,
66 | description: 'Warning button type'
67 | })
68 | .example(/* you can chain more examples */)
69 | ```
70 |
71 | ### Bootstrapping
72 | Because DopeDocs is an Angular app, it must be bootstrapped with all your examples. So create a new entry file for it, like you would with an entry `NgModule`.
73 |
74 | ```typescript
75 | import 'core-js'
76 | import 'zone.js'
77 |
78 | import { FormsModule } from '@angular/forms'
79 | import { createDopeDocs } from '@angularclass/dope-docs'
80 | import { UIModule } from './app/ui'
81 |
82 | // this takes in all the options needed to bootstrap your dope docs
83 | createDopeDocs({
84 | // The module from your app that has all the components exported.
85 | ngModule: UIModule,
86 |
87 | /*
88 | * This is the markdown for your Docs entry route. Will be the landing page
89 | */
90 | entryMarkdown: `# My Teams' Components`,
91 |
92 | /*
93 | * Any NgModules your NgModule will need. Great if your project is a library
94 | * and depends on the host app for these modules
95 | */
96 | ngModuleImports: [
97 | FormsModule
98 | ],
99 | /*
100 | * This function must return all the modules in your app that have docs.
101 | * Above is an example of how to do so pragmatically using webpack`s `require.context`.
102 | * If you're not using Webpack, or want to be explicit, you can just require
103 | * every file individually or just import them all up top :sunglasses: and return them in an array here
104 | */
105 | loadUIGuides() {
106 | const context = (require as any).context('./', true, /\.doc\.ts/) // this works because all my examples have .doc.ts paths
107 | return context.keys().map(context).map((mod: any) => mod.default)
108 | }
109 | })
110 | ```
111 |
112 | ### Setup build
113 | Last step is to setup configuration. Create a `dope.js` on your root file. Your Angular app probably has a specific build setup, so DopeDocs will use that setup to build itself and your App.
114 |
115 | ```js
116 | module.exports = {
117 | // your webpack config. Will be used to build the app.
118 | webpackConfig: require('./webpack.config')
119 | // the path to the Dope docs entry file you created above
120 | entry: './src/dope-docs.ts'
121 | }
122 | ```
123 |
124 |
125 | ## Inspiration
126 | * [Component Lab](https://github.com/synapse-wireless-labs/component-lab) We literally copied this and added more features and updated dependencies.
127 | * [React Storybook](https://github.com/storybooks/storybook)
128 |
129 | ## Contributing
130 | PR's and issues welcome!
131 |
132 | There aren't any tests associated with this, so your code will be look at carefully
133 |
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/src/webpack/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | UI Guide
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Loading...
20 |
21 |
22 |
23 |
24 |
76 |
224 |
225 |
226 |
--------------------------------------------------------------------------------
/src/assets/flexbox.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v2.1.3 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden],template{display:none}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}a{background:0 0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0}body{box-sizing:border-box;padding:0;margin:0;font-size:18px;font-family:HelveticaNeue-Light,"Helvetica Neue Light","Helvetica Neue",Helvetica,Arial,"Lucida Grande",sans-serif;font-weight:400;background:#EEE;line-height:1.4rem}h1,h2,h3,h4,h5,h6{font-family:Gibson,HelveticaNeue-Light,"Helvetica Neue Light","Helvetica Neue",Helvetica,Arial,"Lucida Grande",sans-serif;color:#001A33}h2{font-size:2rem;margin:1rem 0}:focus{outline-color:transparent;outline-style:none}h2+p{margin:0 0 2rem}a{text-decoration:none;color:#007FFF;padding:0 0 .2rem;font-weight:700}a:hover{color:#007FFF}pre{overflow-x:auto;padding:1.25em;border:1px solid #e6e6e6;border-left-width:5px;margin:1.6em 0;font-size:.875em;background:#fcfcfc;white-space:pre;word-wrap:normal}code{color:#007FFF}.layout{display:flex;min-height:100vh;flex-direction:column}.page-nav{box-sizing:border-box;position:fixed;padding:.5rem;width:100%;background:0 0}.page{z-index:0;background:#EEE}.wrap{box-sizing:border-box;max-width:1200px;margin:0 auto}.page-section{padding-top:3rem;margin-bottom:3rem}.page-features{width:100%;background:#001a33;overflow:scroll}.menu-button{position:fixed;top:.75rem;right:.75rem;z-index:1;box-sizing:border-box;padding:.45rem;height:3rem;width:3rem;background:#FFF;border:1px solid transparent;user-select:none}.menu-button:hover{border:1px solid #007FFF;border-radius:2px}.menu-button:active{background:#EEE;border:1px solid transparent}.open{transform:translate3d(-15rem,0,0)}.menu-button-icon{width:2rem;height:2rem}.hero{box-sizing:border-box;padding:2rem;background:#FFF;border:1px solid #FFF;border-radius:2px}.hero-headline{font-size:3rem;white-space:nowrap;margin-bottom:0}.hero-copy{font-size:1rem;margin-bottom:0;padding:0 2rem;text-align:center}.slide-menu{display:block;position:fixed;overflow:auto;top:0;right:0;bottom:0;height:100%;width:250px}.menu{box-sizing:border-box;padding-bottom:5rem;background:#001a33}.menu-header{box-sizing:border-box;padding:3rem 3rem 0;color:#eee}.menu-list{margin:0;padding:0;list-style:none}.menu-list-item{height:3rem;line-height:3rem;font-size:1rem;color:#007FFF;background:0 0;transition:all .2s ease-in}.menu-link{box-sizing:border-box;padding-left:3rem;display:block;color:#007FFF;transition:color .2s ease-in}.menu-link:hover{color:#3298ff;border-bottom:0}.link-top{align-self:flex-end}.button{position:relative;display:inline-block;box-sizing:border-box;min-width:11rem;padding:0 4rem;margin:1rem;height:3rem;line-height:3rem;border:1px solid #007FFF;border-radius:2px;color:#007FFF;font-size:1.25rem;transition:background-color,.15s}.button:hover{background:#39F;border-color:#39F;color:#FFF;text-shadow:0 1px #007FFF}.button:active{background:#007FFF;color:#FFF;border-top:2px solid #06C}.box,.box-first,.box-large,.box-nested,.box-row{position:relative;box-sizing:border-box;min-height:1rem;margin-bottom:0;background:#007FFF;border:1px solid #FFF;border-radius:2px;overflow:hidden;text-align:center;color:#fff}.box-row{margin-bottom:1rem}.box-first{background:#06C;border-color:#007FFF}.box-nested{background:#036;border-color:#007FFF}.box-large{height:8rem}.box-container{box-sizing:border-box;padding:.5rem}.page-footer{box-sizing:border-box;padding-bottom:3rem}.tag{color:#000;font-weight:400}.end{text-align:end}.invisible-xs{display:none;visibility:hidden}.visible-xs{display:block;visibility:visible}@media only screen and (min-width:48rem){body{font-size:16px}.slide-menu{width:25%}.open{transform:translate3d(0,0,0)}.hero-headline{font-size:6rem;margin-bottom:2rem}.hero-copy{font-size:1.25rem;margin-bottom:2rem}.box,.box-first,.box-large,.box-nested,.box-row{padding:1rem}.invisible-md{display:none;visibility:hidden}.visible-md{display:block;visibility:visible}}.container,.container-fluid{margin-right:auto;margin-left:auto}.container-fluid{padding-right:2rem;padding-left:2rem}.row{box-sizing:border-box;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-.5rem;margin-left:-.5rem}.row.reverse{-webkit-box-orient:horizontal;-webkit-box-direction:reverse;-ms-flex-direction:row-reverse;flex-direction:row-reverse}.col.reverse{-webkit-box-orient:vertical;-webkit-box-direction:reverse;-ms-flex-direction:column-reverse;flex-direction:column-reverse}.col-xs,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-offset-0,.col-xs-offset-1,.col-xs-offset-10,.col-xs-offset-11,.col-xs-offset-12,.col-xs-offset-2,.col-xs-offset-3,.col-xs-offset-4,.col-xs-offset-5,.col-xs-offset-6,.col-xs-offset-7,.col-xs-offset-8,.col-xs-offset-9{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.col-xs{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.col-xs-1{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.col-xs-2{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.col-xs-3{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.col-xs-4{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.col-xs-5{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.col-xs-6{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.col-xs-7{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.col-xs-8{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.col-xs-9{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.col-xs-10{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.col-xs-11{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.col-xs-12{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.col-xs-offset-0{margin-left:0}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-11{margin-left:91.66666667%}.start-xs{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.center-xs{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.end-xs{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.top-xs{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.middle-xs{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.bottom-xs{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.around-xs{-ms-flex-pack:distribute;justify-content:space-around}.between-xs{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.first-xs{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.last-xs{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}@media only screen and (min-width:48em){.container{width:49rem}.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-offset-0,.col-sm-offset-1,.col-sm-offset-10,.col-sm-offset-11,.col-sm-offset-12,.col-sm-offset-2,.col-sm-offset-3,.col-sm-offset-4,.col-sm-offset-5,.col-sm-offset-6,.col-sm-offset-7,.col-sm-offset-8,.col-sm-offset-9{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.col-sm{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.col-sm-1{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.col-sm-2{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.col-sm-3{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.col-sm-4{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.col-sm-5{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.col-sm-6{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.col-sm-7{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.col-sm-8{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.col-sm-9{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.col-sm-10{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.col-sm-11{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.col-sm-12{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.col-sm-offset-0{margin-left:0}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-11{margin-left:91.66666667%}.start-sm{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.center-sm{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.end-sm{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.top-sm{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.middle-sm{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.bottom-sm{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.around-sm{-ms-flex-pack:distribute;justify-content:space-around}.between-sm{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.first-sm{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.last-sm{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}}@media only screen and (min-width:64em){.container{width:65rem}.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-offset-0,.col-md-offset-1,.col-md-offset-10,.col-md-offset-11,.col-md-offset-12,.col-md-offset-2,.col-md-offset-3,.col-md-offset-4,.col-md-offset-5,.col-md-offset-6,.col-md-offset-7,.col-md-offset-8,.col-md-offset-9{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.col-md{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.col-md-1{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.col-md-2{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.col-md-3{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.col-md-4{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.col-md-5{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.col-md-6{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.col-md-7{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.col-md-8{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.col-md-9{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.col-md-10{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.col-md-11{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.col-md-12{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.col-md-offset-0{margin-left:0}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-3{margin-left:25%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-6{margin-left:50%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-9{margin-left:75%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-11{margin-left:91.66666667%}.start-md{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.center-md{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.end-md{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.top-md{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.middle-md{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.bottom-md{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.around-md{-ms-flex-pack:distribute;justify-content:space-around}.between-md{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.first-md{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.last-md{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}}@media only screen and (min-width:75em){.container{width:76rem}.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-offset-0,.col-lg-offset-1,.col-lg-offset-10,.col-lg-offset-11,.col-lg-offset-12,.col-lg-offset-2,.col-lg-offset-3,.col-lg-offset-4,.col-lg-offset-5,.col-lg-offset-6,.col-lg-offset-7,.col-lg-offset-8,.col-lg-offset-9{box-sizing:border-box;-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;padding-right:.5rem;padding-left:.5rem}.col-lg{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1;-ms-flex-preferred-size:0;flex-basis:0;max-width:100%}.col-lg-1{-ms-flex-preferred-size:8.33333333%;flex-basis:8.33333333%;max-width:8.33333333%}.col-lg-2{-ms-flex-preferred-size:16.66666667%;flex-basis:16.66666667%;max-width:16.66666667%}.col-lg-3{-ms-flex-preferred-size:25%;flex-basis:25%;max-width:25%}.col-lg-4{-ms-flex-preferred-size:33.33333333%;flex-basis:33.33333333%;max-width:33.33333333%}.col-lg-5{-ms-flex-preferred-size:41.66666667%;flex-basis:41.66666667%;max-width:41.66666667%}.col-lg-6{-ms-flex-preferred-size:50%;flex-basis:50%;max-width:50%}.col-lg-7{-ms-flex-preferred-size:58.33333333%;flex-basis:58.33333333%;max-width:58.33333333%}.col-lg-8{-ms-flex-preferred-size:66.66666667%;flex-basis:66.66666667%;max-width:66.66666667%}.col-lg-9{-ms-flex-preferred-size:75%;flex-basis:75%;max-width:75%}.col-lg-10{-ms-flex-preferred-size:83.33333333%;flex-basis:83.33333333%;max-width:83.33333333%}.col-lg-11{-ms-flex-preferred-size:91.66666667%;flex-basis:91.66666667%;max-width:91.66666667%}.col-lg-12{-ms-flex-preferred-size:100%;flex-basis:100%;max-width:100%}.col-lg-offset-0{margin-left:0}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-11{margin-left:91.66666667%}.start-lg{-webkit-box-pack:start;-ms-flex-pack:start;justify-content:flex-start;text-align:start}.center-lg{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;text-align:center}.end-lg{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;text-align:end}.top-lg{-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.middle-lg{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.bottom-lg{-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end}.around-lg{-ms-flex-pack:distribute;justify-content:space-around}.between-lg{-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.first-lg{-webkit-box-ordinal-group:0;-ms-flex-order:-1;order:-1}.last-lg{-webkit-box-ordinal-group:2;-ms-flex-order:1;order:1}}
2 |
--------------------------------------------------------------------------------