├── .eslintrc
├── .gitignore
├── README.md
├── app
├── components
│ ├── FilterLinks.js
│ ├── Footer.js
│ ├── TextEditor.js
│ ├── TodoApp.html
│ ├── TodoApp.js
│ └── TodoView.js
├── directives
│ └── FocusOn.js
├── index.js
└── services
│ ├── Filters.js
│ └── TodoStore.js
├── dist
├── css
│ ├── base.css
│ └── index.css
└── index.html
├── gulpfile.babel.js
├── karma.conf.js
└── package.json
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser" : "babel-eslint",
3 | "rules" : {
4 | "strict" : 0,
5 | "quotes" : [1, "single"],
6 | "new-cap" : 0,
7 | "eol-last" : 0,
8 | "no-shadow" : 0,
9 | "no-trailing-spaces" : 0,
10 | "no-underscore-dangle" : 0,
11 | "no-unused-vars" : [2, {
12 | "args" : "none"
13 | }]
14 | },
15 | "globals" : {
16 | "window" : false,
17 | "mina" : false,
18 | "protractor" : false,
19 | "require" : false
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bower_components
2 | node_modules
3 | .DS_Store
4 | .hold
5 | .sass-cache
6 | *.swp
7 | .build
8 | dist/bundle.js
9 | npm-debug.log
10 | busters.json
11 | coverage
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ng-forward-playground
2 | A playground for testing early builds of ng-forward
3 |
4 | To get started, clone this repository and ng-forward
5 |
6 | 1. In your terminal, go to the folder where you cloned ng-forward
7 | 2. Run `npm link` to begin the linking process
8 | 3. Go to the folder where you cloned ng-forward-playground
9 | 4. Run `npm install`
10 | 5. Run `npm link ng-forward` to finish linking ng-forward to the playground
11 | 6. Run `gulp` to start a development server
12 |
--------------------------------------------------------------------------------
/app/components/FilterLinks.js:
--------------------------------------------------------------------------------
1 | import {Inject, Component} from 'ng-forward';
2 | import Filters from '../services/Filters';
3 |
4 | @Component({
5 | selector: 'filterLinks',
6 | template:
7 | `
8 |
13 | `
14 | })
15 | @Inject(Filters)
16 | export default class FilterLinks {
17 |
18 | constructor(filters) {
19 | this.filters = filters;
20 | }
21 | }
--------------------------------------------------------------------------------
/app/components/Footer.js:
--------------------------------------------------------------------------------
1 | import {Component, EventEmitter} from 'ng-forward';
2 | import FilterLinks from './FilterLinks';
3 |
4 | @Component({
5 | selector: 'footer',
6 | inputs: ['remainingCount', 'completedCount'],
7 | outputs: ['clearCompleted'],
8 | directives: [FilterLinks],
9 | template:
10 | `
11 | {{footer.remainingCount}}
12 |
13 |
14 |
15 |
16 | `
17 | })
18 | export default class Footer {
19 | constructor() {
20 | this.clearCompleted = new EventEmitter();
21 | }
22 | }
--------------------------------------------------------------------------------
/app/components/TextEditor.js:
--------------------------------------------------------------------------------
1 | import {Component, EventEmitter, Inject} from 'ng-forward';
2 | import FocusOn from '../directives/FocusOn';
3 |
4 | const ESC_KEY = 27;
5 | const ENTER_KEY = 13;
6 |
7 | @Component({
8 | selector: 'text-editor',
9 | inputs: ['value', 'placeholder', 'inputClasses', 'focusOn'],
10 | outputs: ['start', 'enter', 'end', 'abort'],
11 | directives: [FocusOn],
12 | template:
13 | ``
19 | })
20 | @Inject('$element')
21 | export default class TextEditor {
22 | constructor($element) {
23 | this.$input = $element.find('input')[0];
24 | this.start = new EventEmitter();
25 | this.enter = new EventEmitter();
26 | this.end = new EventEmitter();
27 | this.abort = new EventEmitter();
28 | }
29 |
30 | keyup(keyCode) {
31 | if(keyCode === ENTER_KEY) {
32 | this.enter.next();
33 | }
34 | if(keyCode === ESC_KEY) {
35 | this.abort.next();
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/components/TodoApp.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
34 |
35 |
41 |
--------------------------------------------------------------------------------
/app/components/TodoApp.js:
--------------------------------------------------------------------------------
1 | import {Inject, Component} from 'ng-forward';
2 | import {TodoStore} from '../services/TodoStore';
3 | import Footer from './Footer';
4 | import TextEditor from './TextEditor';
5 | import TodoView from './TodoView';
6 | import Filters from '../services/Filters';
7 |
8 | @Component({
9 | selector: 'todo-app',
10 | providers: [TodoStore, Filters],
11 | directives: [TextEditor, TodoView, Footer],
12 | template: require('./TodoApp.html')
13 | })
14 | @Inject(TodoStore, Filters)
15 | export default class TodoApp {
16 |
17 | todoStore: TodoStore;
18 | newTodoTitle: string;
19 |
20 | constructor(todoStore, filters) {
21 | this.filters = filters;
22 | this.todoStore = todoStore;
23 | this.newTodoTitle = '';
24 | }
25 |
26 | addTodo() {
27 | let newTodoTitle = this.newTodoTitle && this.newTodoTitle.trim();
28 | if (newTodoTitle) {
29 | this.todoStore.add(newTodoTitle);
30 | this.newTodoTitle = '';
31 | }
32 | }
33 |
34 | getFilteredTodos() {
35 | return this.todoStore.todos.filter(this.filters.current());
36 | }
37 | }
--------------------------------------------------------------------------------
/app/components/TodoView.js:
--------------------------------------------------------------------------------
1 | import {Component, EventEmitter} from 'ng-forward';
2 |
3 | @Component({
4 | selector: 'todo-view',
5 | inputs: ['todo'],
6 | outputs: ['titleChange', 'completedChange', 'remove'],
7 | template:
8 | `
9 |
10 |
11 |
12 |
13 |
14 |
23 | `
24 | })
25 | export default class TodoView {
26 | constructor() {
27 | this.titleChange = new EventEmitter();
28 | this.completedChange = new EventEmitter();
29 | this.remove = new EventEmitter();
30 | }
31 |
32 | updateTitle() {
33 | this.titleChange.next();
34 | this.todo.editing = false;
35 | }
36 |
37 | saveOriginal() {
38 | this.originalTitle = this.todo.title;
39 | }
40 |
41 | resetTodo() {
42 | this.todo.title = this.originalTitle;
43 | this.todo.editing = false;
44 | }
45 | }
--------------------------------------------------------------------------------
/app/directives/FocusOn.js:
--------------------------------------------------------------------------------
1 | import {Directive, Inject} from 'ng-forward';
2 |
3 | /**
4 | * Directive that places focus on the element it is applied to when the
5 | * expression it binds to evaluates to true
6 | */
7 | @Directive({ selector: '[focus-on]' })
8 | @Inject('$timeout', '$scope', '$attrs', '$element')
9 | export default class FocusOn {
10 | constructor($timeout, $scope, $attrs, $element) {
11 | $scope.$watch($attrs.focusOn, newVal => {
12 | if (newVal) {
13 | $timeout(() => $element[0].focus(), 0, false);
14 | }
15 | });
16 | }
17 | }
--------------------------------------------------------------------------------
/app/index.js:
--------------------------------------------------------------------------------
1 | import 'babel/polyfill';
2 | import 'angular';
3 | import 'zone.js';
4 | import {bootstrap} from 'ng-forward';
5 | import TodoApp from './components/TodoApp';
6 |
7 | bootstrap(TodoApp);
--------------------------------------------------------------------------------
/app/services/Filters.js:
--------------------------------------------------------------------------------
1 | import {Injectable, Inject} from 'ng-forward';
2 |
3 | @Injectable()
4 | @Inject('$location')
5 | export default class Filters {
6 | constructor($location) {
7 | this.$location = $location;
8 |
9 | this.filters = {
10 | '': () => true,
11 | 'active': todo => !todo.completed,
12 | 'completed': todo => todo.completed
13 | };
14 | }
15 |
16 | currentName() {
17 | // strip off leading forward slash
18 | return this.$location.path().replace(/^\//, '');
19 | }
20 |
21 | current() {
22 | return this.filters[this.currentName()];
23 | }
24 |
25 | isCurrent(filterName) {
26 | return (filterName) === this.currentName();
27 | }
28 | }
--------------------------------------------------------------------------------
/app/services/TodoStore.js:
--------------------------------------------------------------------------------
1 | import {Injectable} from 'ng-forward';
2 |
3 | /* global localStorage */
4 | export class Todo {
5 | completed: Boolean;
6 | title: String;
7 | constructor(title: String, completed = false) {
8 | this.completed = completed;
9 | this.title = title.trim();
10 | }
11 | }
12 |
13 | @Injectable()
14 | export class TodoStore {
15 |
16 | todos: Array;
17 |
18 | constructor() {
19 | // Load the todos from local storage
20 | let persistedTodos = JSON.parse(localStorage.getItem('todos') || '[]');
21 | this.todos = persistedTodos.map( (todo) => {
22 | return new Todo(todo.title, todo.completed);
23 | });
24 | }
25 |
26 | countCompleted() {
27 | let count = this.todos.reduce((count, todo) => todo.completed ? count + 1 : count, 0);
28 | return count;
29 | }
30 |
31 | allCompleted() {
32 | return this.todos.length === this.countCompleted();
33 | }
34 |
35 | countRemaining() {
36 | return this.todos.length - this.countCompleted();
37 | }
38 |
39 | setAllTo(completed) {
40 | this.todos.forEach((t: Todo) => t.completed = completed);
41 | this.save();
42 | }
43 |
44 | removeCompleted() {
45 | this.todos = this.todos.filter((todo: Todo) => !todo.completed);
46 | this.save();
47 | }
48 |
49 | remove(todoToRemove: Todo) {
50 | this.todos = this.todos.filter((todo: Todo) => todoToRemove !== todo);
51 | this.save();
52 | }
53 |
54 | add(title: String, completed: boolean) {
55 | this.todos.push(new Todo(title, completed));
56 | this.save();
57 | }
58 |
59 | save() {
60 | localStorage.setItem('todos', JSON.stringify(this.todos));
61 | }
62 | }
--------------------------------------------------------------------------------
/dist/css/base.css:
--------------------------------------------------------------------------------
1 | hr {
2 | margin: 20px 0;
3 | border: 0;
4 | border-top: 1px dashed #c5c5c5;
5 | border-bottom: 1px dashed #f7f7f7;
6 | }
7 |
8 | .learn a {
9 | font-weight: normal;
10 | text-decoration: none;
11 | color: #b83f45;
12 | }
13 |
14 | .learn a:hover {
15 | text-decoration: underline;
16 | color: #787e7e;
17 | }
18 |
19 | .learn h3,
20 | .learn h4,
21 | .learn h5 {
22 | margin: 10px 0;
23 | font-weight: 500;
24 | line-height: 1.2;
25 | color: #000;
26 | }
27 |
28 | .learn h3 {
29 | font-size: 24px;
30 | }
31 |
32 | .learn h4 {
33 | font-size: 18px;
34 | }
35 |
36 | .learn h5 {
37 | margin-bottom: 0;
38 | font-size: 14px;
39 | }
40 |
41 | .learn ul {
42 | padding: 0;
43 | margin: 0 0 30px 25px;
44 | }
45 |
46 | .learn li {
47 | line-height: 20px;
48 | }
49 |
50 | .learn p {
51 | font-size: 15px;
52 | font-weight: 300;
53 | line-height: 1.3;
54 | margin-top: 0;
55 | margin-bottom: 0;
56 | }
57 |
58 | #issue-count {
59 | display: none;
60 | }
61 |
62 | .quote {
63 | border: none;
64 | margin: 20px 0 60px 0;
65 | }
66 |
67 | .quote p {
68 | font-style: italic;
69 | }
70 |
71 | .quote p:before {
72 | content: '“';
73 | font-size: 50px;
74 | opacity: .15;
75 | position: absolute;
76 | top: -20px;
77 | left: 3px;
78 | }
79 |
80 | .quote p:after {
81 | content: '”';
82 | font-size: 50px;
83 | opacity: .15;
84 | position: absolute;
85 | bottom: -42px;
86 | right: 3px;
87 | }
88 |
89 | .quote footer {
90 | position: absolute;
91 | bottom: -40px;
92 | right: 0;
93 | }
94 |
95 | .quote footer img {
96 | border-radius: 3px;
97 | }
98 |
99 | .quote footer a {
100 | margin-left: 5px;
101 | vertical-align: middle;
102 | }
103 |
104 | .speech-bubble {
105 | position: relative;
106 | padding: 10px;
107 | background: rgba(0, 0, 0, .04);
108 | border-radius: 5px;
109 | }
110 |
111 | .speech-bubble:after {
112 | content: '';
113 | position: absolute;
114 | top: 100%;
115 | right: 30px;
116 | border: 13px solid transparent;
117 | border-top-color: rgba(0, 0, 0, .04);
118 | }
119 |
120 | .learn-bar > .learn {
121 | position: absolute;
122 | width: 272px;
123 | top: 8px;
124 | left: -300px;
125 | padding: 10px;
126 | border-radius: 5px;
127 | background-color: rgba(255, 255, 255, .6);
128 | transition-property: left;
129 | transition-duration: 500ms;
130 | }
131 |
132 | @media (min-width: 899px) {
133 | .learn-bar {
134 | width: auto;
135 | padding-left: 300px;
136 | }
137 |
138 | .learn-bar > .learn {
139 | left: 8px;
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/dist/css/index.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | button {
8 | margin: 0;
9 | padding: 0;
10 | border: 0;
11 | background: none;
12 | font-size: 100%;
13 | vertical-align: baseline;
14 | font-family: inherit;
15 | font-weight: inherit;
16 | color: inherit;
17 | -webkit-appearance: none;
18 | appearance: none;
19 | -webkit-font-smoothing: antialiased;
20 | -moz-font-smoothing: antialiased;
21 | font-smoothing: antialiased;
22 | }
23 |
24 | body {
25 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
26 | line-height: 1.4em;
27 | background: #f5f5f5;
28 | color: #4d4d4d;
29 | min-width: 230px;
30 | max-width: 550px;
31 | margin: 0 auto;
32 | -webkit-font-smoothing: antialiased;
33 | -moz-font-smoothing: antialiased;
34 | font-smoothing: antialiased;
35 | font-weight: 300;
36 | }
37 |
38 | button,
39 | input[type="checkbox"] {
40 | outline: none;
41 | }
42 |
43 | .hidden {
44 | display: none;
45 | }
46 |
47 | .todoapp {
48 | background: #fff;
49 | margin: 130px 0 40px 0;
50 | position: relative;
51 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
52 | 0 25px 50px 0 rgba(0, 0, 0, 0.1);
53 | }
54 |
55 | .todoapp input::-webkit-input-placeholder {
56 | font-style: italic;
57 | font-weight: 300;
58 | color: #e6e6e6;
59 | }
60 |
61 | .todoapp input::-moz-placeholder {
62 | font-style: italic;
63 | font-weight: 300;
64 | color: #e6e6e6;
65 | }
66 |
67 | .todoapp input::input-placeholder {
68 | font-style: italic;
69 | font-weight: 300;
70 | color: #e6e6e6;
71 | }
72 |
73 | .todoapp h1 {
74 | position: absolute;
75 | top: -155px;
76 | width: 100%;
77 | font-size: 100px;
78 | font-weight: 100;
79 | text-align: center;
80 | color: rgba(175, 47, 47, 0.15);
81 | -webkit-text-rendering: optimizeLegibility;
82 | -moz-text-rendering: optimizeLegibility;
83 | text-rendering: optimizeLegibility;
84 | }
85 |
86 | .new-todo,
87 | .edit {
88 | position: relative;
89 | margin: 0;
90 | width: 100%;
91 | font-size: 24px;
92 | font-family: inherit;
93 | font-weight: inherit;
94 | line-height: 1.4em;
95 | border: 0;
96 | outline: none;
97 | color: inherit;
98 | padding: 6px;
99 | border: 1px solid #999;
100 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
101 | box-sizing: border-box;
102 | -webkit-font-smoothing: antialiased;
103 | -moz-font-smoothing: antialiased;
104 | font-smoothing: antialiased;
105 | }
106 |
107 | .new-todo {
108 | padding: 16px 16px 16px 60px;
109 | border: none;
110 | background: rgba(0, 0, 0, 0.003);
111 | box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
112 | }
113 |
114 | .main {
115 | position: relative;
116 | z-index: 2;
117 | border-top: 1px solid #e6e6e6;
118 | }
119 |
120 | label[for='toggle-all'] {
121 | display: none;
122 | }
123 |
124 | .toggle-all {
125 | position: absolute;
126 | top: -55px;
127 | left: -12px;
128 | width: 60px;
129 | height: 34px;
130 | text-align: center;
131 | border: none; /* Mobile Safari */
132 | }
133 |
134 | .toggle-all:before {
135 | content: '❯';
136 | font-size: 22px;
137 | color: #e6e6e6;
138 | padding: 10px 27px 10px 27px;
139 | }
140 |
141 | .toggle-all:checked:before {
142 | color: #737373;
143 | }
144 |
145 | .todo-list {
146 | margin: 0;
147 | padding: 0;
148 | list-style: none;
149 | }
150 |
151 | .todo-list li {
152 | position: relative;
153 | font-size: 24px;
154 | border-bottom: 1px solid #ededed;
155 | }
156 |
157 | .todo-list li:last-child {
158 | border-bottom: none;
159 | }
160 |
161 | .todo-list li.editing {
162 | border-bottom: none;
163 | padding: 0;
164 | }
165 |
166 | .todo-list li.editing .edit {
167 | display: block;
168 | width: 506px;
169 | padding: 13px 17px 12px 17px;
170 | margin: 0 0 0 43px;
171 | }
172 |
173 | .todo-list li.editing .view {
174 | display: none;
175 | }
176 |
177 | .todo-list li .toggle {
178 | text-align: center;
179 | width: 40px;
180 | /* auto, since non-WebKit browsers doesn't support input styling */
181 | height: auto;
182 | position: absolute;
183 | top: 0;
184 | bottom: 0;
185 | margin: auto 0;
186 | border: none; /* Mobile Safari */
187 | -webkit-appearance: none;
188 | appearance: none;
189 | }
190 |
191 | .todo-list li .toggle:after {
192 | content: url('data:image/svg+xml;utf8,');
193 | }
194 |
195 | .todo-list li .toggle:checked:after {
196 | content: url('data:image/svg+xml;utf8,');
197 | }
198 |
199 | .todo-list li label {
200 | white-space: pre;
201 | word-break: break-word;
202 | padding: 15px 60px 15px 15px;
203 | margin-left: 45px;
204 | display: block;
205 | line-height: 1.2;
206 | transition: color 0.4s;
207 | }
208 |
209 | .todo-list li.completed label {
210 | color: #d9d9d9;
211 | text-decoration: line-through;
212 | }
213 |
214 | .todo-list li .destroy {
215 | display: none;
216 | position: absolute;
217 | top: 0;
218 | right: 10px;
219 | bottom: 0;
220 | width: 40px;
221 | height: 40px;
222 | margin: auto 0;
223 | font-size: 30px;
224 | color: #cc9a9a;
225 | margin-bottom: 11px;
226 | transition: color 0.2s ease-out;
227 | }
228 |
229 | .todo-list li .destroy:hover {
230 | color: #af5b5e;
231 | }
232 |
233 | .todo-list li .destroy:after {
234 | content: '×';
235 | }
236 |
237 | .todo-list li:hover .destroy {
238 | display: block;
239 | }
240 |
241 | .todo-list li .edit {
242 | display: none;
243 | }
244 |
245 | .todo-list li.editing:last-child {
246 | margin-bottom: -1px;
247 | }
248 |
249 | .footer {
250 | color: #777;
251 | padding: 10px 15px;
252 | height: 20px;
253 | text-align: center;
254 | border-top: 1px solid #e6e6e6;
255 | }
256 |
257 | .footer:before {
258 | content: '';
259 | position: absolute;
260 | right: 0;
261 | bottom: 0;
262 | left: 0;
263 | height: 50px;
264 | overflow: hidden;
265 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
266 | 0 8px 0 -3px #f6f6f6,
267 | 0 9px 1px -3px rgba(0, 0, 0, 0.2),
268 | 0 16px 0 -6px #f6f6f6,
269 | 0 17px 2px -6px rgba(0, 0, 0, 0.2);
270 | }
271 |
272 | .todo-count {
273 | float: left;
274 | text-align: left;
275 | }
276 |
277 | .todo-count strong {
278 | font-weight: 300;
279 | }
280 |
281 | .filters {
282 | margin: 0;
283 | padding: 0;
284 | list-style: none;
285 | position: absolute;
286 | right: 0;
287 | left: 0;
288 | }
289 |
290 | .filters li {
291 | display: inline;
292 | }
293 |
294 | .filters li a {
295 | color: inherit;
296 | margin: 3px;
297 | padding: 3px 7px;
298 | text-decoration: none;
299 | border: 1px solid transparent;
300 | border-radius: 3px;
301 | }
302 |
303 | .filters li a.selected,
304 | .filters li a:hover {
305 | border-color: rgba(175, 47, 47, 0.1);
306 | }
307 |
308 | .filters li a.selected {
309 | border-color: rgba(175, 47, 47, 0.2);
310 | }
311 |
312 | .clear-completed,
313 | html .clear-completed:active {
314 | float: right;
315 | position: relative;
316 | line-height: 20px;
317 | text-decoration: none;
318 | cursor: pointer;
319 | position: relative;
320 | }
321 |
322 | .clear-completed:hover {
323 | text-decoration: underline;
324 | }
325 |
326 | .info {
327 | margin: 65px auto 0;
328 | color: #bfbfbf;
329 | font-size: 10px;
330 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
331 | text-align: center;
332 | }
333 |
334 | .info p {
335 | line-height: 1;
336 | }
337 |
338 | .info a {
339 | color: inherit;
340 | text-decoration: none;
341 | font-weight: 400;
342 | }
343 |
344 | .info a:hover {
345 | text-decoration: underline;
346 | }
347 |
348 | /*
349 | Hack to remove background from Mobile Safari.
350 | Can't use it globally since it destroys checkboxes in Firefox
351 | */
352 | @media screen and (-webkit-min-device-pixel-ratio:0) {
353 | .toggle-all,
354 | .todo-list li .toggle {
355 | background: none;
356 | }
357 |
358 | .todo-list li .toggle {
359 | height: 40px;
360 | }
361 |
362 | .toggle-all {
363 | -webkit-transform: rotate(90deg);
364 | transform: rotate(90deg);
365 | -webkit-appearance: none;
366 | appearance: none;
367 | }
368 | }
369 |
370 | @media (max-width: 430px) {
371 | .footer {
372 | height: 50px;
373 | }
374 |
375 | .filters {
376 | bottom: 10px;
377 | }
378 | }
379 |
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/gulpfile.babel.js:
--------------------------------------------------------------------------------
1 | /*global __dirname */
2 | import babelify from 'babelify';
3 | import browserify from 'browserify';
4 | import buffer from 'vinyl-buffer';
5 | import eslint from 'gulp-eslint';
6 | import gulp from 'gulp';
7 | import gulpwatch from 'gulp-watch';
8 | import gutil from 'gulp-util';
9 | import historyApiFallback from 'connect-history-api-fallback';
10 | import sequence from 'run-sequence';
11 | import source from 'vinyl-source-stream';
12 | import stringify from 'stringify';
13 | import watchify from 'watchify';
14 | import {server as karma} from 'karma';
15 | import browsersync from 'browser-sync';
16 |
17 | function handleError(task){
18 | return function(err) {
19 | gutil.beep();
20 | gutil.log(err);
21 | this.emit('end');
22 | };
23 | }
24 |
25 | function Build(watch, done){
26 | var b;
27 | function transform(){
28 | if( !b ){
29 | b = browserify('./app/index.js', {
30 | debug: true,
31 | paths: ['./node_modules', './app/'],
32 | cache: {},
33 | packageCache: {},
34 | fullPaths: true
35 | });
36 |
37 | if(watch){
38 | b = watchify(b);
39 | }
40 |
41 | b.transform(babelify.configure({
42 | stage: 0
43 | }));
44 |
45 | b.transform(stringify(['.html']));
46 | }
47 |
48 | function bundle(){
49 | let stream = b.bundle()
50 | .on('error', handleError('Browserify'))
51 | .pipe(source('bundle.js'))
52 | .pipe(buffer())
53 | .pipe(gulp.dest('./dist'));
54 |
55 | stream.on('end', browsersync.reload);
56 |
57 | return stream;
58 | }
59 |
60 | return bundle();
61 | }
62 |
63 | gulp.task('bundle', ['lint'], function bundleTask(){
64 | return transform();
65 | });
66 |
67 | gulp.task('bundle-light', function bundleLightTask(){
68 | return transform();
69 | });
70 |
71 | gulp.task('test', function(done){
72 | karma.start({
73 | configFile: __dirname + '/karma.conf.js',
74 | singleRun: true
75 | }, done);
76 | });
77 |
78 | function tdd(){
79 | karma.start({
80 | configFile: __dirname + '/karma.conf.js',
81 | singleRun: false,
82 | reporters: ['mocha']
83 | });
84 | }
85 |
86 | gulp.task('setup-watchers', function(){
87 | gulpwatch(['app/**/*.js', 'app/**/*.html', 'node_modules/ng-forward/**/*.js'], function(){
88 | gulp.run('bundle');
89 | });
90 |
91 | gulp.run('serve');
92 | tdd();
93 | });
94 |
95 | if(watch)
96 | {
97 | sequence('bundle', 'setup-watchers', done);
98 | }
99 | else
100 | {
101 | sequence('bundle', 'test', done);
102 | }
103 | }
104 |
105 | function serve(){
106 | browsersync.init({
107 | port: 2000,
108 | ui: {
109 | port: 2001
110 | },
111 | server: {
112 | baseDir: './dist',
113 | middleware: [historyApiFallback]
114 | }
115 | });
116 | }
117 |
118 | gulp.task('serve', serve);
119 |
120 | gulp.task('default', function(done){
121 | Build(true, done);
122 | });
123 |
124 | gulp.task('build', function(done){
125 | Build(false, done);
126 | });
127 |
128 | gulp.task('lint', function(){
129 | return gulp.src(['app/**/*.js', '!app/**/*.spec.js', '!app/**/*.e2e.js'])
130 | .pipe(eslint())
131 | .pipe(eslint.format());
132 | });
133 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | /* global module */
2 | module.exports = function(karma) {
3 | karma.set({
4 | autoWatch: true,
5 | basePath: '',
6 | frameworks: ['mocha', 'browserify'],
7 | files: [ 'app/**/*.spec.js' ],
8 | exclude: [],
9 | port: 9018,
10 | runnerPort: 9101,
11 | browsers: [
12 | 'Chrome'
13 | ],
14 | reporters: ['mocha'],
15 | singleRun: false,
16 | colors: true,
17 | logLevel: karma.LOG_INFO,
18 | preprocessors: {
19 | 'app/**/*.js': ['browserify']
20 | },
21 | browserify: {
22 | debug: true,
23 | transform: [
24 | ['stringify'],
25 | ['babelify', {
26 | stage: 0
27 | }]
28 | ]
29 | }
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ng-forward-playground",
3 | "author": "Mike Ryan",
4 | "repository": "https://github.com/ngUpgraders/ng-forward-playground",
5 | "version": "0.1.0",
6 | "devDependencies": {
7 | "angular-mocks": "^1.4.3",
8 | "babel": "^5.8.19",
9 | "babel-eslint": "^4.0.5",
10 | "babelify": "^6.1.3",
11 | "browser-sync": "^2.8.1",
12 | "browserify": "^11.0.1",
13 | "chai": "^2.2.0",
14 | "chai-as-promised": "^4.3.0",
15 | "chai-jquery": "^2.0.0",
16 | "connect-history-api-fallback": "0.0.5",
17 | "eslint": "^0.24.0",
18 | "gulp": "^3.9.0",
19 | "gulp-eslint": "^0.13.2",
20 | "gulp-util": "^3.0.4",
21 | "gulp-watch": "^4.2.4",
22 | "karma": "^0.12.31",
23 | "karma-browserify": "^4.1.2",
24 | "karma-chrome-launcher": "^0.1.5",
25 | "karma-mocha": "^0.1.10",
26 | "karma-mocha-reporter": "^1.0.2",
27 | "mocha": "^2.2.4",
28 | "run-sequence": "^1.0.2",
29 | "sinon": "^1.14.1",
30 | "sinon-chai": "^2.7.0",
31 | "sinon-promise": "^0.1.3",
32 | "stringify": "^3.1.0",
33 | "vinyl-buffer": "^1.0.0",
34 | "vinyl-source-stream": "^1.1.0",
35 | "watchify": "^3.3.1"
36 | },
37 | "scripts": {
38 | "postinstall": "npm dedupe"
39 | },
40 | "dependencies": {
41 | "angular": "^1.4.3",
42 | "angular-decorators": "^1.0.0",
43 | "angular-ui-router": "^0.2.15",
44 | "lodash": "^3.9.3",
45 | "rx": "^2.5.3",
46 | "todomvc-app-css": "^2.0.1",
47 | "todomvc-common": "^1.0.2",
48 | "zone.js": "^0.5.2"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------