├── .gitignore ├── README.md ├── custom.d.ts ├── mocha.opts ├── package.json ├── src ├── assets │ └── flexbox.css ├── boostrap.ts ├── cli.ts ├── components │ ├── guides-list.component.ts │ ├── index.ts │ ├── markdown.component.ts │ ├── prism.component.ts │ ├── ui-api.component.ts │ ├── ui-example.component.ts │ └── ui-guide.component.ts ├── generate-hosts.ts ├── index.ts ├── interfaces.ts ├── routes.ts ├── services │ ├── index.ts │ ├── sandbox.service.ts │ └── ui-guide.service.ts ├── styles.ts ├── ui-guide-builder.ts ├── ui-guide.module.ts ├── views │ ├── components.component.ts │ ├── entry.component.ts │ ├── index.ts │ ├── root.component.ts │ └── ui-guide-preview.component.ts └── webpack │ ├── build.ts │ ├── index.html │ ├── index.ts │ └── serve.ts ├── test-shim.js ├── tsconfig.cli.json ├── tsconfig.esm.json ├── tsconfig.json ├── tslint.json ├── webpack.config.js ├── webpack.testing.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | *.tmp 4 | DS* 5 | typings 6 | dist 7 | .vscode 8 | *.pid 9 | *.seed 10 | build/Release 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /custom.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*' 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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 | 18 |
19 |
20 | 31 |
32 | 33 |
34 | 37 | 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 |
11 |

Inputs

12 |
13 |
14 | {{inputLabel}} 15 |
16 |
17 |
18 |
19 | {{input.name}} 20 |
21 |
22 | {{input.description}} 23 |
24 |
25 | {{input.type}} 26 |
27 |
28 | {{input.default}} 29 |
30 |
31 |
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/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 |
26 |
27 |
28 |
29 | 33 | 34 | 37 | 38 |
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/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/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/index.ts: -------------------------------------------------------------------------------- 1 | export * from './interfaces' 2 | export { docsFor, createDopeDocs, DocsBuilder} from './ui-guide-builder' 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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 |
13 | 14 |
15 | 16 |
17 |
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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/webpack/index.ts: -------------------------------------------------------------------------------- 1 | export * from './serve' 2 | export * from './build' 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tsconfig.cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/cli" 5 | }, 6 | "files": [ 7 | "./src/cli.ts" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------