├── .editorconfig
├── .gitattributes
├── .gitignore
├── .travis.yml
├── generators
├── app
│ ├── index.js
│ └── templates
│ │ ├── _package.json
│ │ ├── editorconfig
│ │ ├── gitignore
│ │ ├── license
│ │ ├── readme.md
│ │ ├── scripts
│ │ ├── build-prod.sh
│ │ └── dev-server.sh
│ │ ├── src
│ │ ├── components
│ │ │ ├── site-footer.js
│ │ │ ├── site-header.js
│ │ │ ├── todo-filter.js
│ │ │ ├── todo-form.js
│ │ │ ├── todo-item.js
│ │ │ └── todo-list.js
│ │ ├── index.js
│ │ ├── models
│ │ │ └── todos.js
│ │ ├── routes.js
│ │ ├── styles
│ │ │ └── main.css
│ │ └── views
│ │ │ ├── about.js
│ │ │ ├── home.js
│ │ │ └── todos.js
│ │ └── static
│ │ └── index.html
├── component
│ ├── index.js
│ └── templates
│ │ └── _component.js
├── element
│ ├── index.js
│ └── templates
│ │ └── _element.js
├── generate.js
├── model
│ ├── index.js
│ └── templates
│ │ └── _model.js
└── view
│ ├── index.js
│ └── templates
│ └── _view.js
├── license
├── package.json
├── readme.md
└── tests
└── generators
├── app.js
├── component.js
├── element.js
├── modal.js
└── view.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [{package.json,*.yml}]
12 | indent_style = space
13 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.js text eol=lf
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 | *.pid.lock
11 |
12 | # Directory for instrumented libs generated by jscoverage/JSCover
13 | lib-cov
14 |
15 | # Coverage directory used by tools like istanbul
16 | coverage
17 |
18 | # nyc test coverage
19 | .nyc_output
20 |
21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
22 | .grunt
23 |
24 | # node-waf configuration
25 | .lock-wscript
26 |
27 | # Compiled binary addons (http://nodejs.org/api/addons.html)
28 | build/Release
29 | dist
30 |
31 | # Dependency directories
32 | node_modules
33 | jspm_packages
34 |
35 | # Optional npm cache directory
36 | .npm
37 |
38 | # Optional REPL history
39 | .node_repl_history
40 | Contact GitHub API Training Shop Blog About
41 |
42 | # Mac OSX
43 | *.DS_Store
44 | .AppleDouble
45 | .LSOverride
46 |
47 | # Icon must end with two \r
48 | Icon
49 |
50 |
51 | # Thumbnails
52 | ._*
53 |
54 | # Files that might appear in the root of a volume
55 | .DocumentRevisions-V100
56 | .fseventsd
57 | .Spotlight-V100
58 | .TemporaryItems
59 | .Trashes
60 | .VolumeIcon.icns
61 | .com.apple.timemachine.donotpresent
62 |
63 | # Directories potentially created on remote AFP share
64 | .AppleDB
65 | .AppleDesktop
66 | Network Trash Folder
67 | Temporary Items
68 | .apdisk
69 |
70 |
71 | # Linux
72 |
73 | # temporary files which can be created if a process still has a handle open of a deleted file
74 | .fuse_hidden*
75 |
76 | # KDE directory preferences
77 | .directory
78 |
79 | # Linux trash folder which might appear on any partition or disk
80 | .Trash-*
81 |
82 | # Windows
83 |
84 | # Windows image file caches
85 | Thumbs.db
86 | ehthumbs.db
87 |
88 | # Folder config file
89 | Desktop.ini
90 |
91 | # Recycle Bin used on file shares
92 | $RECYCLE.BIN/
93 |
94 | # Windows Installer files
95 | *.cab
96 | *.msi
97 | *.msm
98 | *.msp
99 |
100 | # Windows shortcuts
101 | *.lnk
102 |
103 | # VS Code
104 | .vscode
105 |
106 | #IntelliJ
107 |
108 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
109 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
110 |
111 | # User-specific stuff:
112 | .idea/workspace.xml
113 | .idea/tasks.xml
114 | .idea/dictionaries
115 | .idea/vcs.xml
116 | .idea/jsLibraryMappings.xml
117 |
118 | # Sensitive or high-churn files:
119 | .idea/dataSources.ids
120 | .idea/dataSources.xml
121 | .idea/dataSources.local.xml
122 | .idea/sqlDataSources.xml
123 | .idea/dynamic.xml
124 | .idea/uiDesigner.xml
125 |
126 | # Gradle:
127 | .idea/gradle.xml
128 | .idea/libraries
129 |
130 | # Mongo Explorer plugin:
131 | .idea/mongoSettings.xml
132 |
133 | ## File-based project format:
134 | *.iws
135 |
136 | ## Plugin-specific files:
137 |
138 | # IntelliJ
139 | /out/
140 |
141 | # mpeltonen/sbt-idea plugin
142 | .idea_modules/
143 |
144 | # JIRA plugin
145 | atlassian-ide-plugin.xml
146 |
147 | # Crashlytics plugin (for Android Studio and IntelliJ)
148 | com_crashlytics_export_strings.xml
149 | crashlytics.properties
150 | crashlytics-build.properties
151 | fabric.properties
152 | node_modules
153 | temp
154 | npm-debug.log
155 | .vscode
156 | .idea
157 | .history
158 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '6'
4 | notifications:
5 | email: false
6 |
--------------------------------------------------------------------------------
/generators/app/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const yeoman = require('yeoman-generator')
3 | const kebab = require('lodash.kebabcase')
4 |
5 | module.exports = yeoman.Base.extend({
6 | prompting: function () {
7 | return this.prompt([{
8 | name: 'projectName',
9 | message: 'What do you want to name your project?',
10 | default: this.appname.replace(/\s/g, '-'),
11 | filter: x => kebab(x)
12 | }, {
13 | name: 'projectDescription',
14 | message: 'A brief description of your app?',
15 | default: ''
16 | }, {
17 | name: 'githubUsername',
18 | message: 'What is your GitHub username?',
19 | store: true,
20 | validate: x => x.length > 0 ? true : 'You have to provide a username'
21 | }]).then(props =>
22 | this.config.set({
23 | template: Object.assign({}, props, {
24 | name: this.user.git.name(),
25 | email: this.user.git.email()
26 | })
27 | })
28 | )
29 | },
30 | writing: function () {
31 | const mv = (from, to) => {
32 | this.fs.move(this.destinationPath(from), this.destinationPath(to))
33 | }
34 |
35 | this.fs.copyTpl([
36 | `${this.templatePath()}/**`
37 | ], this.destinationPath(), this.config.get('template'))
38 | mv('editorconfig', '.editorconfig')
39 | mv('gitignore', '.gitignore')
40 | mv('_package.json', 'package.json')
41 | },
42 | install: function () {
43 | this.spawnCommandSync('git', ['init'])
44 | this.installDependencies({ bower: false })
45 | }
46 | })
47 |
--------------------------------------------------------------------------------
/generators/app/templates/_package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "<%= projectName %>",
3 | "version": "0.0.0",
4 | "description": "<%= projectDescription %>",
5 | "license": "MIT",
6 | "repository": "<%= githubUsername %>/<%= projectName %>",
7 | "scripts": {
8 | "start": "npm run dev:server",
9 | "predeploy": "npm test && npm run build:prod",
10 | "deploy": "surge dist || npm i surge && surge dist",
11 | "dev:server": "scripts/dev-server.sh",
12 | "build:prod": "scripts/build-prod.sh",
13 | "lint": "standard --verbose | snazzy",
14 | "test": "npm run lint"
15 | },
16 | "dependencies": {
17 | "choo": "^3.2.0"
18 | },
19 | "devDependencies": {
20 | "browserify": "^13.0.1",
21 | "budo": "8.3.0",
22 | "choo-log": "^1.4.0",
23 | "es2040": "1.2.2",
24 | "envify": "^3.4.1",
25 | "normalize.css": "^4.2.0",
26 | "sheetify": "^5.0.3",
27 | "standard": "^7.1.2",
28 | "snazzy": "^4.0.0",
29 | "uglifyify": "^3.0.2",
30 | "unassertify": "^2.0.3",
31 | "yo-yoify": "^3.3.0"
32 | },
33 | "standard": {
34 | "ignore": [
35 | "scripts"
36 | ]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/generators/app/templates/editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [{package.json,*.yml}]
12 | indent_style = space
13 |
--------------------------------------------------------------------------------
/generators/app/templates/gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 | *.pid.lock
11 |
12 | # Directory for instrumented libs generated by jscoverage/JSCover
13 | lib-cov
14 |
15 | # Coverage directory used by tools like istanbul
16 | coverage
17 |
18 | # nyc test coverage
19 | .nyc_output
20 |
21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
22 | .grunt
23 |
24 | # node-waf configuration
25 | .lock-wscript
26 |
27 | # Compiled binary addons (http://nodejs.org/api/addons.html)
28 | build/Release
29 | dist
30 |
31 | # Dependency directories
32 | node_modules
33 | jspm_packages
34 |
35 | # Optional npm cache directory
36 | .npm
37 |
38 | # Optional REPL history
39 | .node_repl_history
40 | Contact GitHub API Training Shop Blog About
41 |
42 | # Mac OSX
43 | *.DS_Store
44 | .AppleDouble
45 | .LSOverride
46 |
47 | # Icon must end with two \r
48 | Icon
49 |
50 |
51 | # Thumbnails
52 | ._*
53 |
54 | # Files that might appear in the root of a volume
55 | .DocumentRevisions-V100
56 | .fseventsd
57 | .Spotlight-V100
58 | .TemporaryItems
59 | .Trashes
60 | .VolumeIcon.icns
61 | .com.apple.timemachine.donotpresent
62 |
63 | # Directories potentially created on remote AFP share
64 | .AppleDB
65 | .AppleDesktop
66 | Network Trash Folder
67 | Temporary Items
68 | .apdisk
69 |
70 |
71 | # Linux
72 |
73 | # temporary files which can be created if a process still has a handle open of a deleted file
74 | .fuse_hidden*
75 |
76 | # KDE directory preferences
77 | .directory
78 |
79 | # Linux trash folder which might appear on any partition or disk
80 | .Trash-*
81 |
82 | # Windows
83 |
84 | # Windows image file caches
85 | Thumbs.db
86 | ehthumbs.db
87 |
88 | # Folder config file
89 | Desktop.ini
90 |
91 | # Recycle Bin used on file shares
92 | $RECYCLE.BIN/
93 |
94 | # Windows Installer files
95 | *.cab
96 | *.msi
97 | *.msm
98 | *.msp
99 |
100 | # Windows shortcuts
101 | *.lnk
102 |
103 | # VS Code
104 | .vscode
105 |
106 | #IntelliJ
107 |
108 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
109 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
110 |
111 | # User-specific stuff:
112 | .idea/workspace.xml
113 | .idea/tasks.xml
114 | .idea/dictionaries
115 | .idea/vcs.xml
116 | .idea/jsLibraryMappings.xml
117 |
118 | # Sensitive or high-churn files:
119 | .idea/dataSources.ids
120 | .idea/dataSources.xml
121 | .idea/dataSources.local.xml
122 | .idea/sqlDataSources.xml
123 | .idea/dynamic.xml
124 | .idea/uiDesigner.xml
125 |
126 | # Gradle:
127 | .idea/gradle.xml
128 | .idea/libraries
129 |
130 | # Mongo Explorer plugin:
131 | .idea/mongoSettings.xml
132 |
133 | ## File-based project format:
134 | *.iws
135 |
136 | ## Plugin-specific files:
137 |
138 | # IntelliJ
139 | /out/
140 |
141 | # mpeltonen/sbt-idea plugin
142 | .idea_modules/
143 |
144 | # JIRA plugin
145 | atlassian-ide-plugin.xml
146 |
147 | # Crashlytics plugin (for Android Studio and IntelliJ)
148 | com_crashlytics_export_strings.xml
149 | crashlytics.properties
150 | crashlytics-build.properties
151 | fabric.properties
--------------------------------------------------------------------------------
/generators/app/templates/license:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) <%= name %>
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/generators/app/templates/readme.md:
--------------------------------------------------------------------------------
1 | # <%= projectName %>
2 | *<%= projectDescription %>*
3 |
4 | ## Contributing
5 |
--------------------------------------------------------------------------------
/generators/app/templates/scripts/build-prod.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Clean distribution directory.
4 | rm -rf dist && mkdir dist && mkdir dist/js
5 | # Copy static files to distribution.
6 | cp -r static/* dist
7 |
8 | # Duplicate index.html as 200.html for Surge pushState routing.
9 | cp static/index.html dist/200.html
10 |
11 | # Bundle the main js file.
12 |
13 | # add -d switch for sourcemapping and debugging production.
14 | NODE_ENV=production browserify -e src/index.js -o dist/js/main.js \
15 | -t envify \
16 | -t sheetify/transform \
17 | -g yo-yoify \
18 | -g unassertify \
19 | -g es2040 \
20 | -g uglifyify | uglifyjs
21 |
22 | echo 'Built dist directory'
23 |
--------------------------------------------------------------------------------
/generators/app/templates/scripts/dev-server.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | NODE_ENV=development budo src/index.js:js/main.js --live \
4 | --open \
5 | --host localhost \
6 | --dir static \
7 | --pushstate \
8 | --title <%= projectName %> \
9 | --port 3000 \
10 | -- -t sheetify/transform -g es2040
11 |
--------------------------------------------------------------------------------
/generators/app/templates/src/components/site-footer.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 |
3 | module.exports = (state, prev, send) => html`
4 |
32 | `
33 |
--------------------------------------------------------------------------------
/generators/app/templates/src/components/site-header.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 |
3 | module.exports = (state, prev, send) => html`
4 |
17 | `
18 |
--------------------------------------------------------------------------------
/generators/app/templates/src/components/todo-filter.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const links = [
3 | {
4 | label: 'All',
5 | action: 'SHOW_ALL'
6 | },
7 | {
8 | label: 'Active',
9 | action: 'SHOW_ACTIVE'
10 | },
11 | {
12 | label: 'Completed',
13 | action: 'SHOW_COMPLETED'
14 | }
15 | ]
16 |
17 | module.exports = (state, prev, send) => html`
18 |
`
34 |
35 |
--------------------------------------------------------------------------------
/generators/app/templates/src/components/todo-form.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 |
3 | module.exports = (state, prev, send) => html`
4 |
17 | `
18 |
--------------------------------------------------------------------------------
/generators/app/templates/src/components/todo-item.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 |
3 | module.exports = ({ done, text }, onClick) => html`
4 |
5 | ${text}
6 |
7 | `
8 |
--------------------------------------------------------------------------------
/generators/app/templates/src/components/todo-list.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const todo = require('./todo-item')
3 |
4 | module.exports = (todos = [], send) => html`
5 |
6 | ${
7 | todos.map(item =>
8 | todo(item, () =>
9 | send('todoDemo:toggle', {id: item.id}
10 | )
11 | )
12 | )
13 | }
14 |
15 | `
16 |
--------------------------------------------------------------------------------
/generators/app/templates/src/index.js:
--------------------------------------------------------------------------------
1 | const sf = require('sheetify')
2 | const choo = require('choo')
3 |
4 | sf('normalize.css', { global: true })
5 | sf('./styles/main.css', { global: true })
6 |
7 | const app = choo()
8 | if (process.env.NODE_ENV !== 'production') {
9 | const log = require('choo-log')
10 | app.use(log())
11 | }
12 |
13 | app.model(require('./models/todos'))
14 | app.router(require('./routes'))
15 |
16 | const tree = app.start()
17 |
18 | document.body.appendChild(tree)
19 |
--------------------------------------------------------------------------------
/generators/app/templates/src/models/todos.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | namespace: 'todoDemo',
3 | state: {
4 | todos: [{
5 | id: 1,
6 | text: 'Learn Choo',
7 | done: false
8 | }],
9 | visibility: 'SHOW_ALL'
10 | },
11 | subscriptions: [],
12 | reducers: {
13 | add: (action, state) => ({
14 | todos: state.todos.concat({
15 | id: state.todos.length + 1,
16 | text: action.text,
17 | done: false
18 | })
19 | }),
20 | toggle: (action, state) => ({
21 | todos: state.todos.map(todo =>
22 | todo.id === action.id ? Object.assign({}, todo, { done: !todo.done }) : todo
23 | )
24 | }),
25 | visibilityFilter: ({visibility}) => ({
26 | visibility
27 | })
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/generators/app/templates/src/routes.js:
--------------------------------------------------------------------------------
1 | module.exports = (route) => [
2 | route('/todos', require('./views/todos')),
3 | route('/about', require('./views/about')),
4 | route('/', require('./views/home'))
5 | ]
6 |
--------------------------------------------------------------------------------
/generators/app/templates/src/styles/main.css:
--------------------------------------------------------------------------------
1 | html {
2 | color: #393939;
3 | }
4 |
5 | a {
6 | color: #b30816;
7 | text-decoration: none;
8 | transition: color 0.3s;
9 | }
10 |
11 | a:hover {
12 | text-decoration: underline;
13 | color: #8a3139;
14 | }
15 |
16 | code {
17 | background: #eee;
18 | color: #8a3139;
19 | padding-left: 2px;
20 | padding-right: 2px;
21 | }
22 |
23 | body {
24 | background: #eee;
25 | }
26 |
27 | .container {
28 | margin: 0 auto;
29 | padding: 0;
30 | max-width: 960px;
31 | width: 80%;
32 | }
33 |
34 | .card {
35 | box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);
36 | }
37 |
38 | .well {
39 | padding: 0.5em;
40 | border: thin solid #bbb;
41 | }
42 | .site-header {
43 | clear: both;
44 | display: block;
45 | margin-bottom: 2em;
46 | height: 50px;
47 | }
48 | .site-header nav {
49 | float: right;
50 | }
51 |
52 | .site-header .container {
53 | clear:both;
54 | vertical-align: center;
55 | overflow: hidden;
56 | line-height: 50px;
57 | max-height: 50px;
58 | padding: 0;
59 | }
60 |
61 | .site-header nav a {
62 | display: inline-block;
63 | min-width: 50px;
64 | text-align: center;
65 | font-size: 13px;
66 | line-height: 50px;
67 | margin-left: 1em;
68 | text-transform: uppercase;
69 | }
70 |
71 | .site-header .brand {
72 | display: inline-block;
73 | color: #666;
74 | }
75 |
76 | .site-header .brand * {
77 | line-height: 50px;
78 | font-size: 22px;
79 | float: left;
80 | margin: 0 0.6em 0 0;
81 | color: inherit;
82 | }
83 |
84 | .site-header .brand:after {
85 | clear: both;
86 | }
87 |
88 | .color-white {
89 | background:white;
90 | }
91 |
92 | .color-white.darken-1 {
93 | background: #eee;
94 | }
95 |
96 | .color-white.darken-2 {
97 | background: #ddd;
98 | }
99 |
100 | .center-text {
101 | text-align: center;
102 | }
103 |
104 | .row {
105 | margin-right: -25px;
106 | margin-left: -25px;
107 | display: block;
108 | overflow: hidden;
109 | }
110 |
111 | .col-half,
112 | .col-two-thirds,
113 | .col-one-third {
114 | float: left;
115 | position: relative;
116 | min-height: 1px;
117 | margin: 0;
118 | }
119 |
120 | .col-half {
121 | width: 50%;
122 | }
123 | .col-one-third {
124 | width: 33%;
125 | }
126 | .col-two-thirds {
127 | width: 66%;
128 | }
129 |
130 | .panel {
131 | margin: 1em;
132 | padding: 1em;
133 | }
134 |
135 | .content {
136 | margin-top: 1em;
137 | }
138 |
139 | p {
140 | line-height: 1.2;
141 | }
142 |
143 | .footer-meta {
144 | overflow: hidden;
145 | height: 50px;
146 | line-height: 50px;
147 | color: rgba(255,255,255,0.8);
148 | background-color: #222;
149 | }
150 |
151 | .site-footer {
152 | margin-top: 20px;
153 | padding: 10px 0 0 0;
154 | background-color: #444;
155 | color: #fff;
156 | }
157 |
158 | .site-footer h5 {
159 | font-size: 18px;
160 | }
161 |
162 | .site-footer a {
163 | color: #eee;
164 | }
165 |
166 | .pull-right {
167 | float: right!important;
168 | }
169 |
170 | .pull-left {
171 | float: left!important;
172 | }
173 |
--------------------------------------------------------------------------------
/generators/app/templates/src/views/about.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const header = require('../components/site-header')
3 | const footer = require('../components/site-footer')
4 |
5 | module.exports = (state, prev, send) => html`
6 |
7 | ${header()}
8 |
9 |
10 | About
11 |
12 | This is a simple about page to show that the routing works! :-)
13 | The source code for this page exists at /src/views/about.js
14 | Happy Coding!
15 | 🚂🚋🚋🚋
16 |
17 |
18 | ${footer()}
19 |
20 | `
21 |
--------------------------------------------------------------------------------
/generators/app/templates/src/views/home.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const header = require('../components/site-header')
3 | const footer = require('../components/site-footer')
4 |
5 | const links = [{
6 | label: 'Generator Docs',
7 | href: 'https://github.com/trainyard/generator-choo',
8 | info: 'How to use this generator'
9 | }, {
10 | label: 'Choo Documentation',
11 | href: 'https://github.com/yoshuawuyts/choo',
12 | info: 'The choo repo has some great documentation'
13 | }, {
14 | label: 'Awesome Choo',
15 | href: 'https://github.com/YerkoPalma/awesome-choo',
16 | info: 'A list of awesome things for choo'
17 | }]
18 | module.exports = (state, prev, send) => html`
19 |
20 | ${header()}
21 |
22 | Congrats!
23 |
24 | You did it!! Now to make something amazing
25 | 🚂🚋🚋🚋
26 |
27 |
28 |
29 |
30 |
31 |
Some Tips:
32 |
33 |
34 | - You can see how this demo works by viewing the
src
folder.
35 | - You can see how state management works by viewing the todos section of this site.
36 | - You can remove the demo at any time using
npm run remove-demo
command.
37 |
38 |
39 |
40 |
41 |
42 |
43 |
More Choo!
44 |
Check out these helpful links
45 |
46 |
47 | ${links.map(({label, href, info}) => html`
48 | -
49 |
50 | ${label}
51 | - ${info}
52 |
`)}
53 |
54 |
55 |
56 |
57 |
58 |
59 | ${footer()}
60 |
61 | `
62 |
--------------------------------------------------------------------------------
/generators/app/templates/src/views/todos.js:
--------------------------------------------------------------------------------
1 | const html = require('choo/html')
2 | const header = require('../components/site-header')
3 | const footer = require('../components/site-footer')
4 | const todoList = require('../components/todo-list')
5 | const todoFilter = require('../components/todo-filter')
6 | const todoForm = require('../components/todo-form')
7 |
8 | const getVisibleTodos = (todos, filter) => {
9 | switch (filter) {
10 | case 'SHOW_ALL':
11 | return todos
12 | case 'SHOW_COMPLETED':
13 | return todos.filter(t => t.done)
14 | case 'SHOW_ACTIVE':
15 | return todos.filter(t => !t.done)
16 | }
17 | }
18 |
19 | module.exports = (state, prev, send) => html`
20 |
21 | ${header()}
22 |
23 |
27 |
28 | ${todoForm(state, prev, send)}
29 | ${todoList(getVisibleTodos(state.todoDemo.todos, state.todoDemo.visibility), send)}
30 | ${todoFilter(state, prev, send)}
31 |
32 |
33 | ${footer()}
34 |
35 | `
36 |
--------------------------------------------------------------------------------
/generators/app/templates/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | <%= projectName %>
7 |
8 |
9 |
10 |
11 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/generators/component/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('yeoman-generator').Base.extend({
2 | do: require('../generate')({
3 | category: 'components',
4 | templateFileName: '_component.js'
5 | })
6 | })
7 |
--------------------------------------------------------------------------------
/generators/component/templates/_component.js:
--------------------------------------------------------------------------------
1 | /* Component: <%= name %> */
2 |
3 | const html = require('choo/html')
4 |
5 | module.exports = (state, prev, send) => html`
6 | Your component here
7 | `
8 |
--------------------------------------------------------------------------------
/generators/element/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('yeoman-generator').Base.extend({
2 | do: require('../generate')({
3 | category: 'elements',
4 | templateFileName: '_element.js'
5 | })
6 | })
7 |
--------------------------------------------------------------------------------
/generators/element/templates/_element.js:
--------------------------------------------------------------------------------
1 | /* Element: <%= name %> */
2 |
3 | const html = require('choo/html')
4 |
5 | module.exports = (props) => html`
6 | Your element here
7 | `
8 |
--------------------------------------------------------------------------------
/generators/generate.js:
--------------------------------------------------------------------------------
1 | const { resolve: resolvePath } = require('path')
2 | const kebab = require('lodash.kebabcase')
3 |
4 | module.exports = ({category, templateFileName}) => function () {
5 | const agent = this
6 | if (!agent.args[0]) {
7 | console.log('Missing paramater')
8 | process.exit(0)
9 | }
10 | agent.projectPath = agent.destinationRoot() || agent.config.get('projectPath') || ''
11 | agent.targetName = agent.args[0]
12 | agent.fileName = `${kebab(agent.targetName)}.js`
13 | agent.targetPath = resolvePath(agent.projectPath, 'src', category, agent.fileName)
14 | agent.fs.copyTpl(
15 | agent.templatePath(templateFileName),
16 | agent.destinationPath(agent.targetPath),
17 | { name: agent.targetName }
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/generators/model/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('yeoman-generator').Base.extend({
2 | do: require('../generate')({
3 | category: 'models',
4 | templateFileName: '_model.js'
5 | })
6 | })
7 |
--------------------------------------------------------------------------------
/generators/model/templates/_model.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | /* namespace the model so that it cannot access any properties and handlers in other models */
3 | namespace: '<%= name %>',
4 | state: {
5 | /* initial values of state inside the model */
6 | // counter: 1
7 | },
8 | reducers: {
9 | /* synchronous operations that modify state. Triggered by actions. Signature of (data, state). */
10 | // add: (action, state) => ({ counter: state.counter + 1})
11 | },
12 | effects: {
13 | // asynchronous operations that don't modify state directly.
14 | // Triggered by actions, can call actions. Signature of (data, state, send, done)
15 | },
16 | subscriptions: {
17 | // asynchronous read-only operations that don't modify state directly.
18 | // Can call actions. Signature of (send, done).
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/generators/view/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const esprima = require('esprima')
3 | const escodegen = require('escodegen')
4 | const fs = require('fs')
5 |
6 | module.exports = require('yeoman-generator').Base.extend({
7 | scaffold: require('../generate')({
8 | category: 'views',
9 | templateFileName: '_view.js'
10 | }),
11 | // Inject the view into the router
12 | end: function () {
13 | // adds a new route to the routes array, which looks like:
14 | // route('/myNewView', require('./views/myNewView'))
15 | const addRoute = `[route('/${this.targetName}', require('./views/${this.targetName}'))]`
16 | const injection = esprima.parse(addRoute).body[0].expression.elements[0]
17 | // if we cannot write to the routes for any reason we should fail silently, because
18 | // the view has been succesfully made.
19 | try {
20 | const routesPath = this.projectPath + '/src/routes.js'
21 | const routes = esprima.parse(require(routesPath))
22 | const injectionTarget = routes.body[0].expression.body.elements
23 |
24 | // duplicates will be ignored
25 | if (injectionTarget.find(expr => expr.arguments[0].value === injection.arguments[0].value)) {
26 | this.log.identical('src/routes.js')
27 | process.exit(0)
28 | }
29 | injectionTarget.push(injection)
30 | const esCodeOptions = {
31 | format: {
32 | indent: { style: ' ' },
33 | semicolons: false
34 | }
35 | }
36 | const data = 'module.exports = ' + escodegen.generate(routes, esCodeOptions) + '\n'
37 |
38 | // Purposely using node fs to avoid write conflict issue.
39 | fs.writeFile(routesPath, data, (err) => {
40 | if (err) {
41 | this.log.skip('src/routes.js')
42 | }
43 | this.log.ok('src/routes.js updated')
44 | })
45 | } catch (e) {
46 | this.log.skip('src/routes.js')
47 | if (process.env.NODE_ENV === 'test') {
48 | throw e
49 | }
50 | process.exit(0)
51 | }
52 | }
53 | })
54 |
--------------------------------------------------------------------------------
/generators/view/templates/_view.js:
--------------------------------------------------------------------------------
1 | /* <%= name %> */
2 |
3 | const html = require('choo/html')
4 |
5 | module.exports = (state, prev, send) => html`
6 |
7 | <%= name %>
8 |
9 | `
10 |
--------------------------------------------------------------------------------
/license:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Juan Soto (juansoto.me)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "generator-choo",
3 | "version": "1.0.6",
4 | "description": "Scaffold a choo app",
5 | "license": "MIT",
6 | "repository": "trainyard/generator-choo",
7 | "contributors": [
8 | {
9 | "name": "Juan Soto",
10 | "email": "juan@juansoto.me",
11 | "url": "http://juansoto.me"
12 | },
13 | {
14 | "name": "Matt McFarland",
15 | "email": "contact@mattmcfarland.com",
16 | "url": "https://github.com/MattMcFarland"
17 | },
18 | {
19 | "name": "Hemanth.HM",
20 | "email": "hemanth.hm@gmail.com",
21 | "url": "https://h3manth.com"
22 | },
23 | {
24 | "name": "Ilya Radchenko",
25 | "email": "ilya@burstcreations.com",
26 | "url": "http://burstcreations.com/"
27 | },
28 | {
29 | "name": "Ronn Ross",
30 | "email": "ronn.ross@gmail.com",
31 | "url": "https://github.com/ronnross"
32 | },
33 | {
34 | "name": "Yerko Palma",
35 | "email": "yerko.palma@usach.cl",
36 | "url": "https://github.com/YerkoPalma"
37 | }
38 | ],
39 | "engines": {
40 | "node": ">=6.0.0"
41 | },
42 | "scripts": {
43 | "prepublish": "npm test",
44 | "deploy": "surge -p dist || npm i surge -D && npm run deploy",
45 | "lint": "standard --verbose | snazzy",
46 | "clean": "rm -rf temp",
47 | "pretest": "npm run clean && npm run lint",
48 | "test": "NODE_ENV=test tape --require ./tests/**/*.js | tap-summary"
49 | },
50 | "files": [
51 | "generators",
52 | "license",
53 | "readme.md"
54 | ],
55 | "keywords": [
56 | "yeoman-generator",
57 | "yeoman",
58 | "boilerplate",
59 | "template",
60 | "scaffold",
61 | "choo"
62 | ],
63 | "dependencies": {
64 | "escodegen": "^1.8.0",
65 | "esprima": "^2.7.2",
66 | "lodash.kebabcase": "^4.0.1",
67 | "yeoman-generator": "^0.24.1"
68 | },
69 | "devDependencies": {
70 | "deep-equal": "^1.0.1",
71 | "eslint": "^3.1.1",
72 | "fs.extra": "^1.3.2",
73 | "snazzy": "^4.0.0",
74 | "standard": "^7.1.2",
75 | "tap-summary": "^3.0.1",
76 | "tape": "^4.6.0",
77 | "yeoman-assert": "^2.2.1",
78 | "yeoman-test": "^1.4.0"
79 | },
80 | "standard": {
81 | "ignore": [
82 | "scripts",
83 | "test.js"
84 | ]
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # generator-choo
2 | [](https://www.npmjs.com/package/generator-choo)
3 | [](https://travis-ci.org/trainyard/generator-choo)
4 | [](https://coveralls.io/github/trainyard/generator-choo?branch=master)
5 | 
6 |
7 | A [Yeoman](http://yeoman.io/) generator for [Choo](https://github.com/yoshuawuyts/choo).
8 |
9 | ### Installation
10 |
11 | Make sure you have [Yeoman](http://yeoman.io/) installed globally, then install the generator.
12 |
13 | ```
14 | npm install -g yo
15 | npm install -g generator-choo
16 | ```
17 |
18 | Awesome, you are now ready to use it!
19 |
20 | ### Usage
21 |
22 | To use, make a new directory, hop into it and then run `yo choo`
23 |
24 | ```
25 | mkdir my-choo-app && cd my-choo-app && yo choo
26 | ```
27 |
28 | Follow the prompts to victory!!! Once the process is complete your app should be ready, you can use the following commands:
29 |
30 | ### NPM Scripts
31 |
32 | - `npm start` -- Starts up a dev server with live reloading
33 | - `npm run build:prod` -- Builds a production ready index.html and bundle.js that you can serve remotely.
34 | - `npm run lint` -- Lints your code.
35 | - `npm run deploy` -- Deploy
36 |
37 | ### Scaffolding
38 |
39 | - `yo choo` -- Create a new choo app
40 | - `yo choo:view` -- Create a new view that is also wired up to the router
41 | - `yo choo:model` -- Create a new choo model.
42 | - `yo choo:component` -- Create a new choo component
43 | - `yo choo:element` -- Create a new choo element
44 |
45 | ### Architecture Generated
46 | Here's the architecture of the generated app.
47 |
48 | ```
49 | dist/ <- Production ready, and auto-generated when you run build:prod
50 | src/ <- Source directory that is built
51 | components <- stateless (mostly) ui components that may be complex
52 | elements <- stateless elements (like components) but smaller
53 | models <- choo models
54 | styles <- css files
55 | views <- whole views or pages
56 | index.js <- main app/entry file
57 | routes.js <- router uses this, routes auto-added with yo choo:view
58 | static/ <- available files that will be in dev server and production server
59 | package.json <- npm package
60 | readme.md <- your readme file
61 | .gitignore <- gitignore
62 | ```
63 |
64 | ### Similar Projects
65 |
66 | - [generator-choo-webpack](https://github.com/danneu/generator-choo-webpack) - the minimal choo + webpack yeoman project generator
67 |
68 |
69 | ### FYI
70 |
71 | [](http://standardjs.com)
72 |
73 |
--------------------------------------------------------------------------------
/tests/generators/app.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const test = require('tape')
4 |
5 | const { resolve: resolvePath } = require('path')
6 | const { run } = require('yeoman-test')
7 | const { file: filesExist, fileContent } = require('yeoman-assert')
8 |
9 | test('choo:app', tap => run(resolvePath(__dirname, '../../generators/app'))
10 | .withPrompts({ projectName: 'test', githubUsername: 'test' })
11 | .on('end', () => {
12 | tap.doesNotThrow(() => {
13 | filesExist([
14 | '.editorconfig',
15 | '.git',
16 | '.gitignore',
17 | 'license',
18 | 'package.json',
19 | 'readme.md',
20 | 'scripts/build-prod.sh',
21 | 'scripts/dev-server.sh',
22 | 'src/index.js',
23 | 'src/routes.js',
24 | 'src/components/site-footer.js',
25 | 'src/components/site-header.js',
26 | 'src/components/todo-filter.js',
27 | 'src/components/todo-form.js',
28 | 'src/components/todo-item.js',
29 | 'src/components/todo-list.js',
30 | 'src/models/todos.js',
31 | 'src/styles/main.css',
32 | 'src/views/about.js',
33 | 'src/views/home.js',
34 | 'src/views/todos.js',
35 | 'static/index.html'
36 | ])
37 | }, 'expected files should exist.')
38 | tap.doesNotThrow(() =>
39 | fileContent('src/components/site-header.js', 'test
'), 'Expected file to contain name in heading')
40 |
41 | tap.end()
42 | })
43 | )
44 |
--------------------------------------------------------------------------------
/tests/generators/component.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const test = require('tape')
4 |
5 | const { resolve: resolvePath } = require('path')
6 | const { run } = require('yeoman-test')
7 | const { file: fileExists, fileContent } = require('yeoman-assert')
8 |
9 | test('choo:component', tap => run(resolvePath(__dirname, '../../generators/component'))
10 | .withArguments(['component-test'])
11 | .on('end', () => {
12 | const expectedPath = 'src/components/component-test.js'
13 |
14 | tap.doesNotThrow(() =>
15 | fileExists(expectedPath), `Expected ${expectedPath} to exist.`)
16 | tap.doesNotThrow(() =>
17 | fileContent(expectedPath, '/* Component: component-test */'), 'Expected file to contain name in heading')
18 | tap.end()
19 | })
20 | )
21 |
--------------------------------------------------------------------------------
/tests/generators/element.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const test = require('tape')
4 |
5 | const { resolve: resolvePath } = require('path')
6 | const { run } = require('yeoman-test')
7 | const { file: fileExists, fileContent } = require('yeoman-assert')
8 |
9 | test('choo:element', tap => run(resolvePath(__dirname, '../../generators/element'))
10 | .withArguments(['element-test'])
11 | .on('end', () => {
12 | const expectedPath = 'src/elements/element-test.js'
13 |
14 | tap.doesNotThrow(() =>
15 | fileExists(expectedPath), `Expected ${expectedPath} to exist.`)
16 | tap.doesNotThrow(() =>
17 | fileContent(expectedPath, '/* Element: element-test */'), 'Expected file to contain name in heading')
18 | tap.end()
19 | })
20 | )
21 |
--------------------------------------------------------------------------------
/tests/generators/modal.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const test = require('tape')
4 |
5 | const { resolve: resolvePath } = require('path')
6 | const { run } = require('yeoman-test')
7 | const { file: fileExists, fileContent } = require('yeoman-assert')
8 |
9 | test('choo:model', tap => run(resolvePath(__dirname, '../../generators/model'))
10 | .withArguments(['model-test'])
11 | .on('end', () => {
12 | const expectedPath = 'src/models/model-test.js'
13 |
14 | tap.doesNotThrow(() =>
15 | fileExists(expectedPath), `Expected ${expectedPath} to exist.`)
16 | tap.doesNotThrow(() =>
17 | fileContent(expectedPath, "namespace: 'model-test'"), 'Expected file to contain namespace')
18 | tap.end()
19 | })
20 | )
21 |
--------------------------------------------------------------------------------
/tests/generators/view.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const test = require('tape')
4 |
5 | const { copyRecursive } = require('fs.extra')
6 | const { resolve: resolvePath } = require('path')
7 | const { run } = require('yeoman-test')
8 | const { file: fileExists, fileContent } = require('yeoman-assert')
9 |
10 | test('choo:view', tap => run(resolvePath(__dirname, '../../generators/view'))
11 | .inTmpDir(function (dir) {
12 | const copyFrom = resolvePath(__dirname, '../../generators/app/templates')
13 | const copyTo = resolvePath(dir)
14 |
15 | copyRecursive(copyFrom, copyTo, this.async())
16 | })
17 | .withArguments(['view-test'])
18 | .on('error', msg => {
19 | console.log('fail', msg)
20 | tap.fail(msg)
21 | tap.end()
22 | })
23 | .on('end', () => {
24 | const expectedPath = 'src/views/view-test.js'
25 |
26 | tap.doesNotThrow(() =>
27 | fileExists(expectedPath), `Expected ${expectedPath} to exist.`)
28 | tap.doesNotThrow(() =>
29 | fileContent(expectedPath, 'view-test
'), 'Expected file to contain name in heading')
30 | const injection = "route('/view-test', require('./views/view-test'))"
31 |
32 | tap.doesNotThrow(() =>
33 | fileContent('src/routes.js', ''), `Expected routes.js to contain ${injection}`)
34 | tap.end()
35 | })
36 | )
37 |
--------------------------------------------------------------------------------