├── .bowerrc
├── .editorconfig
├── .gitattributes
├── .gitignore
├── .jshintrc
├── .travis.yml
├── Gruntfile.js
├── app
├── .buildignore
├── .htaccess
├── 404.html
├── cordova_plugins.json
├── favicon.ico
├── images
│ ├── backgrounds
│ │ └── gradient.jpg
│ └── workouts
│ │ ├── abdominal-crunch.png
│ │ ├── high-knees-running-in-place.png
│ │ ├── jumping-jacks.png
│ │ ├── lunge.png
│ │ ├── plank.png
│ │ ├── push-up-and-rotation.png
│ │ ├── push-up.png
│ │ ├── side-plank.png
│ │ ├── squat.png
│ │ ├── step-up-onto-chair.png
│ │ ├── triceps-dip-on-chair.png
│ │ └── wall-sit.png
├── index.html
├── libraries
│ └── angular-mobile.js
├── robots.txt
├── scripts
│ ├── app.js
│ ├── controllers
│ │ ├── main.js
│ │ ├── rest.js
│ │ └── steps.js
│ ├── filters.js
│ ├── plugins
│ │ └── cordova-analytics.js
│ └── services.js
├── sounds
│ ├── mailerror.wav
│ ├── mailsent.wav
│ ├── newmail.wav
│ ├── tick-rest.wav
│ └── tick.wav
├── styles
│ ├── fonts
│ │ ├── fontawesome-webfont.eot
│ │ ├── fontawesome-webfont.svg
│ │ ├── fontawesome-webfont.ttf
│ │ ├── fontawesome-webfont.woff
│ │ ├── satisfy-webfont.ttf
│ │ └── satisfy-webfont.woff
│ ├── includes
│ │ ├── animations.less
│ │ ├── font-awesome.less
│ │ └── page-transitions.less
│ ├── main.css
│ ├── main.less
│ └── workouts.less
└── views
│ ├── done.html
│ ├── main.html
│ ├── rest.html
│ └── step.html
├── bower.json
├── license.md
├── package.json
├── readme.md
├── takedown
└── 20130506-nytimes.jpg
└── test
├── karma-e2e.conf.js
├── karma.conf.js
├── runner.html
└── spec
└── controllers
└── main.js
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "app/components"
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # Change these settings to your own preference
11 | indent_style = space
12 | indent_size = 2
13 |
14 | # We recommend you to keep these unchanged
15 | end_of_line = lf
16 | charset = utf-8
17 | trim_trailing_whitespace = true
18 | insert_final_newline = true
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .tmp
3 | *.sublime-project
4 | *.sublime-workspace
5 | node_modules
6 | app/components
7 | dist
8 | cordova
9 | sources
10 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 |
3 | "node": true,
4 | "browser": true,
5 | "jquery": true,
6 | "devel": false,
7 | "es5": true,
8 | "esnext": true,
9 | "bitwise": true,
10 |
11 | "camelcase": false,
12 | "curly": false,
13 | "eqeqeq": true,
14 | "immed": true,
15 | "indent": 2,
16 | "latedef": true,
17 | "newcap": true,
18 | "noarg": true,
19 | "quotmark": "single",
20 | "regexp": true,
21 | "undef": true,
22 | "unused": false,
23 | "strict": true,
24 | "trailing": true,
25 | "smarttabs": true,
26 |
27 | "boss": false,
28 | "eqnull": false,
29 | "expr": true,
30 |
31 | "globals": {
32 | "angular": false
33 | }
34 |
35 | }
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
4 |
5 | before_script:
6 | - export DISPLAY=:99.0
7 | - export PHANTOMJS_BIN=/usr/local/phantomjs/bin/phantomjs
8 | - sh -e /etc/init.d/xvfb start
9 | - sleep 3 # give xvfb some time to start
10 | - npm install -g grunt-cli
11 |
12 | script:
13 | - grunt jshint karma:unit
14 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (grunt) {
4 |
5 | // load all grunt tasks
6 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
7 |
8 | // livereload
9 | var lrSnippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;
10 | var mountFolder = function (connect, dir) {
11 | return connect.static(require('path').resolve(dir));
12 | };
13 |
14 | // configurable paths
15 | var yeomanConfig = {
16 | app: 'app',
17 | dist: 'src'
18 | };
19 |
20 | grunt.initConfig({
21 | yeoman: yeomanConfig,
22 | pkg: grunt.file.readJSON('package.json'),
23 | meta: {
24 | banner: '/**\n' +
25 | ' * <%= pkg.description %>\n' +
26 | ' * @version v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' +
27 | ' * @link <%= pkg.homepage %>\n' +
28 | ' * @author <%= pkg.author %>\n' +
29 | ' * @license MIT License, http://www.opensource.org/licenses/MIT\n' +
30 | ' */\n'
31 | },
32 | watch: {
33 | // coffee: {
34 | // files: ['<%= yeoman.app %>/scripts/{,*/}*.coffee'],
35 | // tasks: ['coffee:dist']
36 | // },
37 | // coffeeTest: {
38 | // files: ['test/spec/{,*/}*.coffee'],
39 | // tasks: ['coffee:test']
40 | // },
41 | // compass: {
42 | // files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
43 | // tasks: ['compass']
44 | // },
45 | less: {
46 | files: ['<%= yeoman.app %>/styles/{,*/}*.less'],
47 | tasks: ['less:dist']
48 | },
49 | livereload: {
50 | files: [
51 | '<%= yeoman.app %>/{,*/}*.html',
52 | '{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css',
53 | '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
54 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
55 | ],
56 | tasks: ['livereload']
57 | }
58 | },
59 | connect: {
60 | options: {
61 | port: 9000,
62 | // Change this to '0.0.0.0' to access the server from outside.
63 | hostname: 'localhost'
64 | },
65 | livereload: {
66 | options: {
67 | middleware: function (connect) {
68 | return [
69 | lrSnippet,
70 | mountFolder(connect, '.tmp'),
71 | mountFolder(connect, yeomanConfig.app)
72 | ];
73 | }
74 | }
75 | },
76 | test: {
77 | options: {
78 | middleware: function (connect) {
79 | return [
80 | mountFolder(connect, '.tmp'),
81 | mountFolder(connect, 'test')
82 | ];
83 | }
84 | }
85 | }
86 | },
87 | open: {
88 | server: {
89 | url: 'http://localhost:<%= connect.options.port %>'
90 | }
91 | },
92 | clean: {
93 | dist: {
94 | files: [{
95 | dot: true,
96 | src: [
97 | '.tmp',
98 | '<%= yeoman.dist %>/*',
99 | '!<%= yeoman.dist %>/.git*'
100 | ]
101 | }]
102 | },
103 | server: '.tmp'
104 | },
105 | jshint: {
106 | options: {
107 | jshintrc: '.jshintrc'
108 | },
109 | all: [
110 | 'Gruntfile.js',
111 | '<%= yeoman.app %>/scripts/{,*/}*.js'
112 | ]
113 | },
114 | karma: {
115 | options: {
116 | configFile: 'test/karma.conf.js',
117 | browsers: ['PhantomJS']
118 | },
119 | unit: {
120 | port: 9090,
121 | singleRun: true
122 | },
123 | server: {
124 | autoWatch: true
125 | }
126 | },
127 | // coffee: {
128 | // dist: {
129 | // files: [{
130 | // expand: true,
131 | // cwd: '<%= yeoman.app %>/scripts',
132 | // src: '{,*/}*.coffee',
133 | // dest: '.tmp/scripts',
134 | // ext: '.js'
135 | // }]
136 | // },
137 | // test: {
138 | // files: [{
139 | // expand: true,
140 | // cwd: 'test/spec',
141 | // src: '{,*/}*.coffee',
142 | // dest: '.tmp/spec',
143 | // ext: '.js'
144 | // }]
145 | // }
146 | // },
147 | // compass: {
148 | // options: {
149 | // sassDir: '<%= yeoman.app %>/styles',
150 | // cssDir: '.tmp/styles',
151 | // imagesDir: '<%= yeoman.app %>/images',
152 | // javascriptsDir: '<%= yeoman.app %>/scripts',
153 | // fontsDir: '<%= yeoman.app %>/styles/fonts',
154 | // importPath: '<%= yeoman.app %>/components',
155 | // relativeAssets: true
156 | // },
157 | // dist: {},
158 | // server: {
159 | // options: {
160 | // debugInfo: true
161 | // }
162 | // }
163 | // },
164 | less: {
165 | options: {
166 | paths: ['<%= yeoman.app %>/styles'],
167 | },
168 | dist: {
169 | files: {
170 | '<%= yeoman.app %>/styles/main.css': '<%= yeoman.app %>/styles/main.less'
171 | }
172 | }
173 | },
174 | concat: {
175 | options: {
176 | banner: '<%= meta.banner %>'
177 | },
178 | dist: {
179 | files: {
180 | '<%= yeoman.dist %>/scripts/scripts.js': [
181 | '.tmp/scripts/{,*/}*.js',
182 | '<%= yeoman.app %>/scripts/{,*/}*.js'
183 | ]
184 | }
185 | }
186 | },
187 | useminPrepare: {
188 | html: '<%= yeoman.app %>/index.html',
189 | options: {
190 | dest: '<%= yeoman.dist %>'
191 | }
192 | },
193 | usemin: {
194 | html: ['<%= yeoman.dist %>/{,*/}*.html'],
195 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
196 | options: {
197 | dirs: ['<%= yeoman.dist %>']
198 | }
199 | },
200 | imagemin: {
201 | dist: {
202 | files: [{
203 | expand: true,
204 | cwd: '<%= yeoman.app %>/images',
205 | src: '{,*/}*.{png,jpg,jpeg}',
206 | dest: '<%= yeoman.dist %>/images'
207 | }]
208 | }
209 | },
210 | cssmin: {
211 | dist: {
212 | files: {
213 | '<%= yeoman.dist %>/styles/main.css': [
214 | '.tmp/styles/{,*/}*.css',
215 | '<%= yeoman.app %>/styles/{,*/}*.css'
216 | ]
217 | }
218 | }
219 | },
220 | htmlmin: {
221 | dist: {
222 | options: {
223 | /*removeCommentsFromCDATA: true,
224 | // https://github.com/yeoman/grunt-usemin/issues/44
225 | //collapseWhitespace: true,
226 | collapseBooleanAttributes: true,
227 | removeAttributeQuotes: true,
228 | removeRedundantAttributes: true,
229 | useShortDoctype: true,
230 | removeEmptyAttributes: true,
231 | removeOptionalTags: true*/
232 | },
233 | files: [{
234 | expand: true,
235 | cwd: '<%= yeoman.app %>',
236 | src: ['*.html', 'views/*.html'],
237 | dest: '<%= yeoman.dist %>'
238 | }]
239 | }
240 | },
241 | cdnify: {
242 | dist: {
243 | html: ['<%= yeoman.dist %>/*.html']
244 | }
245 | },
246 | ngmin: {
247 | dist: {
248 | files: [{
249 | expand: true,
250 | cwd: '<%= yeoman.dist %>/scripts',
251 | src: '*.js',
252 | dest: '<%= yeoman.dist %>/scripts'
253 | }]
254 | }
255 | },
256 | uglify: {
257 | options: {
258 | banner: '<%= meta.banner %>',
259 | // report: 'gzip',
260 | useStrict: false
261 | },
262 | dist: {
263 | files: {
264 | '<%= yeoman.dist %>/scripts/scripts.js': [
265 | '<%= yeoman.dist %>/scripts/scripts.js'
266 | ],
267 | '<%= yeoman.dist %>/scripts/libs.js': [
268 | '<%= yeoman.dist %>/scripts/libs.js'
269 | ]
270 | }
271 | }
272 | },
273 | rev: {
274 | dist: {
275 | files: {
276 | src: [
277 | '<%= yeoman.dist %>/scripts/{,*/}*.js',
278 | '<%= yeoman.dist %>/styles/{,*/}*.css',
279 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
280 | '<%= yeoman.dist %>/styles/fonts/*'
281 | ]
282 | }
283 | }
284 | },
285 | copy: {
286 | dist: {
287 | files: [{
288 | expand: true,
289 | dot: true,
290 | cwd: '<%= yeoman.app %>',
291 | dest: '<%= yeoman.dist %>',
292 | src: [
293 | '*.{ico,txt,json}',
294 | // '.htaccess',
295 | // 'components/**/*',
296 | 'sounds/{,*/}*.{wav,mp3}',
297 | 'images/{,*/}*.{gif,webp,png}',
298 | 'styles/fonts/*'
299 | ]
300 | }]
301 | }
302 | }
303 | });
304 |
305 | grunt.renameTask('regarde', 'watch');
306 |
307 | grunt.registerTask('server', [
308 | 'clean:server',
309 | // 'coffee:dist',
310 | // 'compass:server',
311 | 'less:dist',
312 | 'livereload-start',
313 | 'connect:livereload',
314 | 'open',
315 | 'watch'
316 | ]);
317 |
318 | grunt.registerTask('test', [
319 | 'clean:server',
320 | // 'coffee',
321 | // 'compass',
322 | 'connect:test',
323 | 'karma:unit'
324 | ]);
325 |
326 | grunt.registerTask('build', [
327 | 'clean:dist',
328 | 'jshint',
329 | 'test',
330 | // 'coffee',
331 | // 'compass:dist',
332 | 'less:dist',
333 | 'useminPrepare',
334 | 'imagemin',
335 | 'cssmin',
336 | 'htmlmin',
337 | 'concat',
338 | 'copy',
339 | // 'cdnify',
340 | 'ngmin',
341 | 'uglify',
342 | 'rev',
343 | 'usemin'
344 | ]);
345 |
346 | grunt.registerTask('fast-build', [
347 | 'clean:dist',
348 | 'less:dist',
349 | 'useminPrepare',
350 | // 'imagemin',
351 | 'cssmin',
352 | 'htmlmin',
353 | 'concat',
354 | 'copy',
355 | // 'cdnify',
356 | 'ngmin',
357 | 'uglify',
358 | 'rev',
359 | 'usemin'
360 | ]);
361 |
362 | grunt.registerTask('default', ['build']);
363 | };
364 |
--------------------------------------------------------------------------------
/app/.buildignore:
--------------------------------------------------------------------------------
1 | *.coffee
--------------------------------------------------------------------------------
/app/.htaccess:
--------------------------------------------------------------------------------
1 | # Apache configuration file
2 | # httpd.apache.org/docs/2.2/mod/quickreference.html
3 |
4 | # Note .htaccess files are an overhead, this logic should be in your Apache
5 | # config if possible: httpd.apache.org/docs/2.2/howto/htaccess.html
6 |
7 | # Techniques in here adapted from all over, including:
8 | # Kroc Camen: camendesign.com/.htaccess
9 | # perishablepress.com/press/2006/01/10/stupid-htaccess-tricks/
10 | # Sample .htaccess file of CMS MODx: modxcms.com
11 |
12 |
13 | # ----------------------------------------------------------------------
14 | # Better website experience for IE users
15 | # ----------------------------------------------------------------------
16 |
17 | # Force the latest IE version, in various cases when it may fall back to IE7 mode
18 | # github.com/rails/rails/commit/123eb25#commitcomment-118920
19 | # Use ChromeFrame if it's installed for a better experience for the poor IE folk
20 |
21 |
22 | Header set X-UA-Compatible "IE=Edge,chrome=1"
23 | # mod_headers can't match by content-type, but we don't want to send this header on *everything*...
24 |
25 | Header unset X-UA-Compatible
26 |
27 |
28 |
29 |
30 | # ----------------------------------------------------------------------
31 | # Cross-domain AJAX requests
32 | # ----------------------------------------------------------------------
33 |
34 | # Serve cross-domain Ajax requests, disabled by default.
35 | # enable-cors.org
36 | # code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
37 |
38 | #
39 | # Header set Access-Control-Allow-Origin "*"
40 | #
41 |
42 |
43 | # ----------------------------------------------------------------------
44 | # CORS-enabled images (@crossorigin)
45 | # ----------------------------------------------------------------------
46 |
47 | # Send CORS headers if browsers request them; enabled by default for images.
48 | # developer.mozilla.org/en/CORS_Enabled_Image
49 | # blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
50 | # hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/
51 | # wiki.mozilla.org/Security/Reviews/crossoriginAttribute
52 |
53 |
54 |
55 | # mod_headers, y u no match by Content-Type?!
56 |
57 | SetEnvIf Origin ":" IS_CORS
58 | Header set Access-Control-Allow-Origin "*" env=IS_CORS
59 |
60 |
61 |
62 |
63 |
64 | # ----------------------------------------------------------------------
65 | # Webfont access
66 | # ----------------------------------------------------------------------
67 |
68 | # Allow access from all domains for webfonts.
69 | # Alternatively you could only whitelist your
70 | # subdomains like "subdomain.example.com".
71 |
72 |
73 |
74 | Header set Access-Control-Allow-Origin "*"
75 |
76 |
77 |
78 |
79 | # ----------------------------------------------------------------------
80 | # Proper MIME type for all files
81 | # ----------------------------------------------------------------------
82 |
83 | # JavaScript
84 | # Normalize to standard type (it's sniffed in IE anyways)
85 | # tools.ietf.org/html/rfc4329#section-7.2
86 | AddType application/javascript js jsonp
87 | AddType application/json json
88 |
89 | # Audio
90 | AddType audio/ogg oga ogg
91 | AddType audio/mp4 m4a f4a f4b
92 |
93 | # Video
94 | AddType video/ogg ogv
95 | AddType video/mp4 mp4 m4v f4v f4p
96 | AddType video/webm webm
97 | AddType video/x-flv flv
98 |
99 | # SVG
100 | # Required for svg webfonts on iPad
101 | # twitter.com/FontSquirrel/status/14855840545
102 | AddType image/svg+xml svg svgz
103 | AddEncoding gzip svgz
104 |
105 | # Webfonts
106 | AddType application/vnd.ms-fontobject eot
107 | AddType application/x-font-ttf ttf ttc
108 | AddType font/opentype otf
109 | AddType application/x-font-woff woff
110 |
111 | # Assorted types
112 | AddType image/x-icon ico
113 | AddType image/webp webp
114 | AddType text/cache-manifest appcache manifest
115 | AddType text/x-component htc
116 | AddType application/xml rss atom xml rdf
117 | AddType application/x-chrome-extension crx
118 | AddType application/x-opera-extension oex
119 | AddType application/x-xpinstall xpi
120 | AddType application/octet-stream safariextz
121 | AddType application/x-web-app-manifest+json webapp
122 | AddType text/x-vcard vcf
123 | AddType application/x-shockwave-flash swf
124 | AddType text/vtt vtt
125 |
126 |
127 | # ----------------------------------------------------------------------
128 | # Allow concatenation from within specific js and css files
129 | # ----------------------------------------------------------------------
130 |
131 | # e.g. Inside of script.combined.js you could have
132 | #
133 | #
134 | # and they would be included into this single file.
135 |
136 | # This is not in use in the boilerplate as it stands. You may
137 | # choose to use this technique if you do not have a build process.
138 |
139 | #
140 | # Options +Includes
141 | # AddOutputFilterByType INCLUDES application/javascript application/json
142 | # SetOutputFilter INCLUDES
143 | #
144 |
145 | #
146 | # Options +Includes
147 | # AddOutputFilterByType INCLUDES text/css
148 | # SetOutputFilter INCLUDES
149 | #
150 |
151 |
152 | # ----------------------------------------------------------------------
153 | # Gzip compression
154 | # ----------------------------------------------------------------------
155 |
156 |
157 |
158 | # Force deflate for mangled headers developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping/
159 |
160 |
161 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
162 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
163 |
164 |
165 |
166 | # HTML, TXT, CSS, JavaScript, JSON, XML, HTC:
167 |
168 | FilterDeclare COMPRESS
169 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/html
170 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/css
171 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/plain
172 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/xml
173 | FilterProvider COMPRESS DEFLATE resp=Content-Type $text/x-component
174 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/javascript
175 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/json
176 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/xml
177 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/xhtml+xml
178 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/rss+xml
179 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/atom+xml
180 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/vnd.ms-fontobject
181 | FilterProvider COMPRESS DEFLATE resp=Content-Type $image/svg+xml
182 | FilterProvider COMPRESS DEFLATE resp=Content-Type $image/x-icon
183 | FilterProvider COMPRESS DEFLATE resp=Content-Type $application/x-font-ttf
184 | FilterProvider COMPRESS DEFLATE resp=Content-Type $font/opentype
185 | FilterChain COMPRESS
186 | FilterProtocol COMPRESS DEFLATE change=yes;byteranges=no
187 |
188 |
189 |
190 | # Legacy versions of Apache
191 | AddOutputFilterByType DEFLATE text/html text/plain text/css application/json
192 | AddOutputFilterByType DEFLATE application/javascript
193 | AddOutputFilterByType DEFLATE text/xml application/xml text/x-component
194 | AddOutputFilterByType DEFLATE application/xhtml+xml application/rss+xml application/atom+xml
195 | AddOutputFilterByType DEFLATE image/x-icon image/svg+xml application/vnd.ms-fontobject application/x-font-ttf font/opentype
196 |
197 |
198 |
199 |
200 |
201 | # ----------------------------------------------------------------------
202 | # Expires headers (for better cache control)
203 | # ----------------------------------------------------------------------
204 |
205 | # These are pretty far-future expires headers.
206 | # They assume you control versioning with filename-based cache busting
207 | # Additionally, consider that outdated proxies may miscache
208 | # www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/
209 |
210 | # If you don't use filenames to version, lower the CSS and JS to something like
211 | # "access plus 1 week".
212 |
213 |
214 | ExpiresActive on
215 |
216 | # Perhaps better to whitelist expires rules? Perhaps.
217 | ExpiresDefault "access plus 1 month"
218 |
219 | # cache.appcache needs re-requests in FF 3.6 (thanks Remy ~Introducing HTML5)
220 | ExpiresByType text/cache-manifest "access plus 0 seconds"
221 |
222 | # Your document html
223 | ExpiresByType text/html "access plus 0 seconds"
224 |
225 | # Data
226 | ExpiresByType text/xml "access plus 0 seconds"
227 | ExpiresByType application/xml "access plus 0 seconds"
228 | ExpiresByType application/json "access plus 0 seconds"
229 |
230 | # Feed
231 | ExpiresByType application/rss+xml "access plus 1 hour"
232 | ExpiresByType application/atom+xml "access plus 1 hour"
233 |
234 | # Favicon (cannot be renamed)
235 | ExpiresByType image/x-icon "access plus 1 week"
236 |
237 | # Media: images, video, audio
238 | ExpiresByType image/gif "access plus 1 month"
239 | ExpiresByType image/png "access plus 1 month"
240 | ExpiresByType image/jpeg "access plus 1 month"
241 | ExpiresByType video/ogg "access plus 1 month"
242 | ExpiresByType audio/ogg "access plus 1 month"
243 | ExpiresByType video/mp4 "access plus 1 month"
244 | ExpiresByType video/webm "access plus 1 month"
245 |
246 | # HTC files (css3pie)
247 | ExpiresByType text/x-component "access plus 1 month"
248 |
249 | # Webfonts
250 | ExpiresByType application/x-font-ttf "access plus 1 month"
251 | ExpiresByType font/opentype "access plus 1 month"
252 | ExpiresByType application/x-font-woff "access plus 1 month"
253 | ExpiresByType image/svg+xml "access plus 1 month"
254 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
255 |
256 | # CSS and JavaScript
257 | ExpiresByType text/css "access plus 1 year"
258 | ExpiresByType application/javascript "access plus 1 year"
259 |
260 |
261 |
262 |
263 | # ----------------------------------------------------------------------
264 | # Prevent mobile network providers from modifying your site
265 | # ----------------------------------------------------------------------
266 |
267 | # The following header prevents modification of your code over 3G on some
268 | # European providers.
269 | # This is the official 'bypass' suggested by O2 in the UK.
270 |
271 | #
272 | # Header set Cache-Control "no-transform"
273 | #
274 |
275 |
276 | # ----------------------------------------------------------------------
277 | # ETag removal
278 | # ----------------------------------------------------------------------
279 |
280 | # FileETag None is not enough for every server.
281 |
282 | Header unset ETag
283 |
284 |
285 | # Since we're sending far-future expires, we don't need ETags for
286 | # static content.
287 | # developer.yahoo.com/performance/rules.html#etags
288 | FileETag None
289 |
290 |
291 | # ----------------------------------------------------------------------
292 | # Stop screen flicker in IE on CSS rollovers
293 | # ----------------------------------------------------------------------
294 |
295 | # The following directives stop screen flicker in IE on CSS rollovers - in
296 | # combination with the "ExpiresByType" rules for images (see above).
297 |
298 | # BrowserMatch "MSIE" brokenvary=1
299 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
300 | # BrowserMatch "Opera" !brokenvary
301 | # SetEnvIf brokenvary 1 force-no-vary
302 |
303 |
304 | # ----------------------------------------------------------------------
305 | # Set Keep-Alive Header
306 | # ----------------------------------------------------------------------
307 |
308 | # Keep-Alive allows the server to send multiple requests through one
309 | # TCP-connection. Be aware of possible disadvantages of this setting. Turn on
310 | # if you serve a lot of static content.
311 |
312 | #
313 | # Header set Connection Keep-Alive
314 | #
315 |
316 |
317 | # ----------------------------------------------------------------------
318 | # Cookie setting from iframes
319 | # ----------------------------------------------------------------------
320 |
321 | # Allow cookies to be set from iframes (for IE only)
322 | # If needed, specify a path or regex in the Location directive.
323 |
324 | #
325 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
326 | #
327 |
328 |
329 | # ----------------------------------------------------------------------
330 | # Start rewrite engine
331 | # ----------------------------------------------------------------------
332 |
333 | # Turning on the rewrite engine is necessary for the following rules and
334 | # features. FollowSymLinks must be enabled for this to work.
335 |
336 | # Some cloud hosting services require RewriteBase to be set: goo.gl/HOcPN
337 | # If using the h5bp in a subdirectory, use `RewriteBase /foo` instead where
338 | # 'foo' is your directory.
339 |
340 | # If your web host doesn't allow the FollowSymlinks option, you may need to
341 | # comment it out and use `Options +SymLinksOfOwnerMatch`, but be aware of the
342 | # performance impact: http://goo.gl/Mluzd
343 |
344 |
345 | Options +FollowSymlinks
346 | # Options +SymLinksIfOwnerMatch
347 | Options +FollowSymlinks
348 | RewriteEngine On
349 | # RewriteBase /
350 |
351 |
352 |
353 | # ----------------------------------------------------------------------
354 | # Suppress or force the "www." at the beginning of URLs
355 | # ----------------------------------------------------------------------
356 |
357 | # The same content should never be available under two different URLs -
358 | # especially not with and without "www." at the beginning, since this can cause
359 | # SEO problems (duplicate content). That's why you should choose one of the
360 | # alternatives and redirect the other one.
361 |
362 | # By default option 1 (no "www.") is activated.
363 | # no-www.org/faq.php?q=class_b
364 |
365 | # If you'd prefer to use option 2, just comment out all option 1 lines
366 | # and uncomment option 2.
367 |
368 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!
369 |
370 | # ----------------------------------------------------------------------
371 |
372 | # Option 1:
373 | # Rewrite "www.example.com -> example.com".
374 |
375 |
376 | RewriteCond %{HTTPS} !=on
377 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
378 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
379 |
380 |
381 | # ----------------------------------------------------------------------
382 |
383 | # Option 2:
384 | # Rewrite "example.com -> www.example.com".
385 | # Be aware that the following rule might not be a good idea if you use "real"
386 | # subdomains for certain parts of your website.
387 |
388 | #
389 | # RewriteCond %{HTTPS} !=on
390 | # RewriteCond %{HTTP_HOST} !^www\..+$ [NC]
391 | # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
392 | #
393 |
394 |
395 | # ----------------------------------------------------------------------
396 | # Built-in filename-based cache busting
397 | # ----------------------------------------------------------------------
398 |
399 | # If you're not using the build script to manage your filename version revving,
400 | # you might want to consider enabling this, which will route requests for
401 | # /css/style.20110203.css to /css/style.css
402 |
403 | # To understand why this is important and a better idea than all.css?v1231,
404 | # read: github.com/h5bp/html5-boilerplate/wiki/cachebusting
405 |
406 | #
407 | # RewriteCond %{REQUEST_FILENAME} !-f
408 | # RewriteCond %{REQUEST_FILENAME} !-d
409 | # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
410 | #
411 |
412 |
413 | # ----------------------------------------------------------------------
414 | # Prevent SSL cert warnings
415 | # ----------------------------------------------------------------------
416 |
417 | # Rewrite secure requests properly to prevent SSL cert warnings, e.g. prevent
418 | # https://www.example.com when your cert only allows https://secure.example.com
419 |
420 | #
421 | # RewriteCond %{SERVER_PORT} !^443
422 | # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]
423 | #
424 |
425 |
426 | # ----------------------------------------------------------------------
427 | # Prevent 404 errors for non-existing redirected folders
428 | # ----------------------------------------------------------------------
429 |
430 | # without -MultiViews, Apache will give a 404 for a rewrite if a folder of the
431 | # same name does not exist.
432 | # webmasterworld.com/apache/3808792.htm
433 |
434 | Options -MultiViews
435 |
436 |
437 | # ----------------------------------------------------------------------
438 | # Custom 404 page
439 | # ----------------------------------------------------------------------
440 |
441 | # You can add custom pages to handle 500 or 403 pretty easily, if you like.
442 | # If you are hosting your site in subdirectory, adjust this accordingly
443 | # e.g. ErrorDocument 404 /subdir/404.html
444 | ErrorDocument 404 /404.html
445 |
446 |
447 | # ----------------------------------------------------------------------
448 | # UTF-8 encoding
449 | # ----------------------------------------------------------------------
450 |
451 | # Use UTF-8 encoding for anything served text/plain or text/html
452 | AddDefaultCharset utf-8
453 |
454 | # Force UTF-8 for a number of file formats
455 | AddCharset utf-8 .atom .css .js .json .rss .vtt .xml
456 |
457 |
458 | # ----------------------------------------------------------------------
459 | # A little more security
460 | # ----------------------------------------------------------------------
461 |
462 | # To avoid displaying the exact version number of Apache being used, add the
463 | # following to httpd.conf (it will not work in .htaccess):
464 | # ServerTokens Prod
465 |
466 | # "-Indexes" will have Apache block users from browsing folders without a
467 | # default document Usually you should leave this activated, because you
468 | # shouldn't allow everybody to surf through every folder on your server (which
469 | # includes rather private places like CMS system folders).
470 |
471 | Options -Indexes
472 |
473 |
474 | # Block access to "hidden" directories or files whose names begin with a
475 | # period. This includes directories used by version control systems such as
476 | # Subversion or Git.
477 |
478 | RewriteCond %{SCRIPT_FILENAME} -d [OR]
479 | RewriteCond %{SCRIPT_FILENAME} -f
480 | RewriteRule "(^|/)\." - [F]
481 |
482 |
483 | # Block access to backup and source files. These files may be left by some
484 | # text/html editors and pose a great security danger, when anyone can access
485 | # them.
486 |
487 | Order allow,deny
488 | Deny from all
489 | Satisfy All
490 |
491 |
492 | # If your server is not already configured as such, the following directive
493 | # should be uncommented in order to set PHP's register_globals option to OFF.
494 | # This closes a major security hole that is abused by most XSS (cross-site
495 | # scripting) attacks. For more information: http://php.net/register_globals
496 | #
497 | # IF REGISTER_GLOBALS DIRECTIVE CAUSES 500 INTERNAL SERVER ERRORS:
498 | #
499 | # Your server does not allow PHP directives to be set via .htaccess. In that
500 | # case you must make this change in your php.ini file instead. If you are
501 | # using a commercial web host, contact the administrators for assistance in
502 | # doing this. Not all servers allow local php.ini files, and they should
503 | # include all PHP configurations (not just this one), or you will effectively
504 | # reset everything to PHP defaults. Consult www.php.net for more detailed
505 | # information about setting PHP directives.
506 |
507 | # php_flag register_globals Off
508 |
509 | # Rename session cookie to something else, than PHPSESSID
510 | # php_value session.name sid
511 |
512 | # Disable magic quotes (This feature has been DEPRECATED as of PHP 5.3.0 and REMOVED as of PHP 5.4.0.)
513 | # php_flag magic_quotes_gpc Off
514 |
515 | # Do not show you are using PHP
516 | # Note: Move this line to php.ini since it won't work in .htaccess
517 | # php_flag expose_php Off
518 |
519 | # Level of log detail - log all errors
520 | # php_value error_reporting -1
521 |
522 | # Write errors to log file
523 | # php_flag log_errors On
524 |
525 | # Do not display errors in browser (production - Off, development - On)
526 | # php_flag display_errors Off
527 |
528 | # Do not display startup errors (production - Off, development - On)
529 | # php_flag display_startup_errors Off
530 |
531 | # Format errors in plain text
532 | # Note: Leave this setting 'On' for xdebug's var_dump() output
533 | # php_flag html_errors Off
534 |
535 | # Show multiple occurrence of error
536 | # php_flag ignore_repeated_errors Off
537 |
538 | # Show same errors from different sources
539 | # php_flag ignore_repeated_source Off
540 |
541 | # Size limit for error messages
542 | # php_value log_errors_max_len 1024
543 |
544 | # Don't precede error with string (doesn't accept empty string, use whitespace if you need)
545 | # php_value error_prepend_string " "
546 |
547 | # Don't prepend to error (doesn't accept empty string, use whitespace if you need)
548 | # php_value error_append_string " "
549 |
550 | # Increase cookie security
551 |
552 | php_value session.cookie_httponly true
553 |
554 |
--------------------------------------------------------------------------------
/app/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Page Not Found :(
6 |
141 |
142 |
143 |
144 |
Not found :(
145 |
Sorry, but the page you were trying to view does not exist.
146 |
It looks like this was the result of either:
147 |
148 |
a mistyped address
149 |
an out-of-date link
150 |
151 |
154 |
155 |
156 |
157 |
158 |
--------------------------------------------------------------------------------
/app/cordova_plugins.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgcrea/angular-7min/0f242fd731671369c0a9c852067f46a981ec3045/app/favicon.ico
--------------------------------------------------------------------------------
/app/images/backgrounds/gradient.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgcrea/angular-7min/0f242fd731671369c0a9c852067f46a981ec3045/app/images/backgrounds/gradient.jpg
--------------------------------------------------------------------------------
/app/images/workouts/abdominal-crunch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgcrea/angular-7min/0f242fd731671369c0a9c852067f46a981ec3045/app/images/workouts/abdominal-crunch.png
--------------------------------------------------------------------------------
/app/images/workouts/high-knees-running-in-place.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgcrea/angular-7min/0f242fd731671369c0a9c852067f46a981ec3045/app/images/workouts/high-knees-running-in-place.png
--------------------------------------------------------------------------------
/app/images/workouts/jumping-jacks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgcrea/angular-7min/0f242fd731671369c0a9c852067f46a981ec3045/app/images/workouts/jumping-jacks.png
--------------------------------------------------------------------------------
/app/images/workouts/lunge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgcrea/angular-7min/0f242fd731671369c0a9c852067f46a981ec3045/app/images/workouts/lunge.png
--------------------------------------------------------------------------------
/app/images/workouts/plank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgcrea/angular-7min/0f242fd731671369c0a9c852067f46a981ec3045/app/images/workouts/plank.png
--------------------------------------------------------------------------------
/app/images/workouts/push-up-and-rotation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgcrea/angular-7min/0f242fd731671369c0a9c852067f46a981ec3045/app/images/workouts/push-up-and-rotation.png
--------------------------------------------------------------------------------
/app/images/workouts/push-up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgcrea/angular-7min/0f242fd731671369c0a9c852067f46a981ec3045/app/images/workouts/push-up.png
--------------------------------------------------------------------------------
/app/images/workouts/side-plank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgcrea/angular-7min/0f242fd731671369c0a9c852067f46a981ec3045/app/images/workouts/side-plank.png
--------------------------------------------------------------------------------
/app/images/workouts/squat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgcrea/angular-7min/0f242fd731671369c0a9c852067f46a981ec3045/app/images/workouts/squat.png
--------------------------------------------------------------------------------
/app/images/workouts/step-up-onto-chair.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgcrea/angular-7min/0f242fd731671369c0a9c852067f46a981ec3045/app/images/workouts/step-up-onto-chair.png
--------------------------------------------------------------------------------
/app/images/workouts/triceps-dip-on-chair.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgcrea/angular-7min/0f242fd731671369c0a9c852067f46a981ec3045/app/images/workouts/triceps-dip-on-chair.png
--------------------------------------------------------------------------------
/app/images/workouts/wall-sit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgcrea/angular-7min/0f242fd731671369c0a9c852067f46a981ec3045/app/images/workouts/wall-sit.png
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Seven Minute
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/app/libraries/angular-mobile.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @license AngularJS v1.1.5-2a7043f
3 | * (c) 2010-2012 Google, Inc. http://angularjs.org
4 | * License: MIT
5 | */
6 | (function(window, angular, undefined) {
7 | 'use strict';
8 |
9 | /**
10 | * @ngdoc overview
11 | * @name ngMobile
12 | * @description
13 | * Touch events and other mobile helpers.
14 | * Based on jQuery Mobile touch event handling (jquerymobile.com)
15 | */
16 |
17 | // define ngMobile module
18 | var ngMobile = angular.module('ngMobile', []);
19 |
20 | /**
21 | * @ngdoc directive
22 | * @name ngMobile.directive:ngClick
23 | *
24 | * @description
25 | * A more powerful replacement for the default ngClick designed to be used on touchscreen
26 | * devices. Most mobile browsers wait about 300ms after a tap-and-release before sending
27 | * the click event. This version handles them immediately, and then prevents the
28 | * following click event from propagating.
29 | *
30 | * This directive can fall back to using an ordinary click event, and so works on desktop
31 | * browsers as well as mobile.
32 | *
33 | * This directive also sets the CSS class `ng-click-active` while the element is being held
34 | * down (by a mouse click or touch) so you can restyle the depressed element if you wish.
35 | *
36 | * @element ANY
37 | * @param {expression} ngClick {@link guide/expression Expression} to evaluate
38 | * upon tap. (Event object is available as `$event`)
39 | *
40 | * @example
41 |
42 |
43 |
46 | count: {{ count }}
47 |
48 |
49 | */
50 |
51 | ngMobile.config(['$provide', function($provide) {
52 | $provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
53 | // drop the default ngClick directive
54 | $delegate.shift();
55 | return $delegate;
56 | }]);
57 | }]);
58 |
59 | ngMobile.directive('ngClick', ['$parse', '$timeout', '$rootElement',
60 | function($parse, $timeout, $rootElement) {
61 | var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag.
62 | var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers.
63 | var PREVENT_DURATION = 2500; // 2.5 seconds maximum from preventGhostClick call to click
64 | var CLICKBUSTER_THRESHOLD = 25; // 25 pixels in any dimension is the limit for busting clicks.
65 |
66 | var ACTIVE_CLASS_NAME = 'ng-click-active';
67 | var lastPreventedTime;
68 | var touchCoordinates;
69 |
70 |
71 | // TAP EVENTS AND GHOST CLICKS
72 | //
73 | // Why tap events?
74 | // Mobile browsers detect a tap, then wait a moment (usually ~300ms) to see if you're
75 | // double-tapping, and then fire a click event.
76 | //
77 | // This delay sucks and makes mobile apps feel unresponsive.
78 | // So we detect touchstart, touchmove, touchcancel and touchend ourselves and determine when
79 | // the user has tapped on something.
80 | //
81 | // What happens when the browser then generates a click event?
82 | // The browser, of course, also detects the tap and fires a click after a delay. This results in
83 | // tapping/clicking twice. So we do "clickbusting" to prevent it.
84 | //
85 | // How does it work?
86 | // We attach global touchstart and click handlers, that run during the capture (early) phase.
87 | // So the sequence for a tap is:
88 | // - global touchstart: Sets an "allowable region" at the point touched.
89 | // - element's touchstart: Starts a touch
90 | // (- touchmove or touchcancel ends the touch, no click follows)
91 | // - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold
92 | // too long) and fires the user's tap handler. The touchend also calls preventGhostClick().
93 | // - preventGhostClick() removes the allowable region the global touchstart created.
94 | // - The browser generates a click event.
95 | // - The global click handler catches the click, and checks whether it was in an allowable region.
96 | // - If preventGhostClick was called, the region will have been removed, the click is busted.
97 | // - If the region is still there, the click proceeds normally. Therefore clicks on links and
98 | // other elements without ngTap on them work normally.
99 | //
100 | // This is an ugly, terrible hack!
101 | // Yeah, tell me about it. The alternatives are using the slow click events, or making our users
102 | // deal with the ghost clicks, so I consider this the least of evils. Fortunately Angular
103 | // encapsulates this ugly logic away from the user.
104 | //
105 | // Why not just put click handlers on the element?
106 | // We do that too, just to be sure. The problem is that the tap event might have caused the DOM
107 | // to change, so that the click fires in the same position but something else is there now. So
108 | // the handlers are global and care only about coordinates and not elements.
109 |
110 | // Checks if the coordinates are close enough to be within the region.
111 | function hit(x1, y1, x2, y2) {
112 | return Math.abs(x1 - x2) < CLICKBUSTER_THRESHOLD && Math.abs(y1 - y2) < CLICKBUSTER_THRESHOLD;
113 | }
114 |
115 | // Checks a list of allowable regions against a click location.
116 | // Returns true if the click should be allowed.
117 | // Splices out the allowable region from the list after it has been used.
118 | function checkAllowableRegions(touchCoordinates, x, y) {
119 | for (var i = 0; i < touchCoordinates.length; i += 2) {
120 | if (hit(touchCoordinates[i], touchCoordinates[i+1], x, y)) {
121 | touchCoordinates.splice(i, i + 2);
122 | return true; // allowable region
123 | }
124 | }
125 | return false; // No allowable region; bust it.
126 | }
127 |
128 | // Global click handler that prevents the click if it's in a bustable zone and preventGhostClick
129 | // was called recently.
130 | function onClick(event) {
131 | if (Date.now() - lastPreventedTime > PREVENT_DURATION) {
132 | return; // Too old.
133 | }
134 |
135 | var touches = event.touches && event.touches.length ? event.touches : [event];
136 | var x = touches[0].clientX;
137 | var y = touches[0].clientY;
138 | // Work around desktop Webkit quirk where clicking a label will fire two clicks (on the label
139 | // and on the input element). Depending on the exact browser, this second click we don't want
140 | // to bust has either (0,0) or negative coordinates.
141 | if (x < 1 && y < 1) {
142 | return; // offscreen
143 | }
144 |
145 | // Look for an allowable region containing this click.
146 | // If we find one, that means it was created by touchstart and not removed by
147 | // preventGhostClick, so we don't bust it.
148 | if (checkAllowableRegions(touchCoordinates, x, y)) {
149 | return;
150 | }
151 |
152 | // If we didn't find an allowable region, bust the click.
153 | event.stopPropagation();
154 | event.preventDefault();
155 | }
156 |
157 |
158 | // Global touchstart handler that creates an allowable region for a click event.
159 | // This allowable region can be removed by preventGhostClick if we want to bust it.
160 | function onTouchStart(event) {
161 | var touches = event.touches && event.touches.length ? event.touches : [event];
162 | var x = touches[0].clientX;
163 | var y = touches[0].clientY;
164 | touchCoordinates.push(x, y);
165 |
166 | $timeout(function() {
167 | // Remove the allowable region.
168 | for (var i = 0; i < touchCoordinates.length; i += 2) {
169 | if (touchCoordinates[i] == x && touchCoordinates[i+1] == y) {
170 | touchCoordinates.splice(i, i + 2);
171 | return;
172 | }
173 | }
174 | }, PREVENT_DURATION, false);
175 | }
176 |
177 | // On the first call, attaches some event handlers. Then whenever it gets called, it creates a
178 | // zone around the touchstart where clicks will get busted.
179 | function preventGhostClick(x, y) {
180 | if (!touchCoordinates) {
181 | $rootElement[0].addEventListener('click', onClick, true);
182 | $rootElement[0].addEventListener('touchstart', onTouchStart, true);
183 | touchCoordinates = [];
184 | }
185 |
186 | lastPreventedTime = Date.now();
187 |
188 | checkAllowableRegions(touchCoordinates, x, y);
189 | }
190 |
191 | // Actual linking function.
192 | return function(scope, element, attr) {
193 | var clickHandler = $parse(attr.ngClick),
194 | tapping = false,
195 | tapElement, // Used to blur the element after a tap.
196 | startTime, // Used to check if the tap was held too long.
197 | touchStartX,
198 | touchStartY;
199 |
200 | function resetState() {
201 | tapping = false;
202 | element.removeClass(ACTIVE_CLASS_NAME);
203 | }
204 |
205 | element.bind('touchstart', function(event) {
206 | tapping = true;
207 | tapElement = event.target ? event.target : event.srcElement; // IE uses srcElement.
208 | // Hack for Safari, which can target text nodes instead of containers.
209 | if(tapElement.nodeType == 3) {
210 | tapElement = tapElement.parentNode;
211 | }
212 |
213 | element.addClass(ACTIVE_CLASS_NAME);
214 |
215 | startTime = Date.now();
216 |
217 | var touches = event.touches && event.touches.length ? event.touches : [event];
218 | var e = touches[0].originalEvent || touches[0];
219 | touchStartX = e.clientX;
220 | touchStartY = e.clientY;
221 | });
222 |
223 | element.bind('touchmove', function(event) {
224 | resetState();
225 | });
226 |
227 | element.bind('touchcancel', function(event) {
228 | resetState();
229 | });
230 |
231 | element.bind('touchend', function(event) {
232 | var diff = Date.now() - startTime;
233 |
234 | var touches = (event.changedTouches && event.changedTouches.length) ? event.changedTouches :
235 | ((event.touches && event.touches.length) ? event.touches : [event]);
236 | var e = touches[0].originalEvent || touches[0];
237 | var x = e.clientX;
238 | var y = e.clientY;
239 | var dist = Math.sqrt( Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2) );
240 |
241 | if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) {
242 | // Call preventGhostClick so the clickbuster will catch the corresponding click.
243 | preventGhostClick(x, y);
244 |
245 | // Blur the focused element (the button, probably) before firing the callback.
246 | // This doesn't work perfectly on Android Chrome, but seems to work elsewhere.
247 | // I couldn't get anything to work reliably on Android Chrome.
248 | if (tapElement) {
249 | tapElement.blur();
250 | }
251 |
252 | scope.$apply(function() {
253 | // TODO(braden): This is sending the touchend, not a tap or click. Is that kosher?
254 | clickHandler(scope, {$event: event});
255 | });
256 | }
257 |
258 | resetState();
259 | });
260 |
261 | // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
262 | // something else nearby.
263 | element.onclick = function(event) { };
264 |
265 | // Fallback click handler.
266 | // Busted clicks don't get this far, and adding this handler allows ng-tap to be used on
267 | // desktop as well, to allow more portable sites.
268 | element.bind('click', function(event) {
269 | scope.$apply(function() {
270 | clickHandler(scope, {$event: event});
271 | });
272 | });
273 |
274 | element.bind('mousedown', function(event) {
275 | element.addClass(ACTIVE_CLASS_NAME);
276 | });
277 |
278 | element.bind('mousemove mouseup', function(event) {
279 | element.removeClass(ACTIVE_CLASS_NAME);
280 | });
281 |
282 | };
283 | }]);
284 |
285 | /**
286 | * @ngdoc directive
287 | * @name ngMobile.directive:ngSwipeLeft
288 | *
289 | * @description
290 | * Specify custom behavior when an element is swiped to the left on a touchscreen device.
291 | * A leftward swipe is a quick, right-to-left slide of the finger.
292 | * Though ngSwipeLeft is designed for touch-based devices, it will work with a mouse click and drag too.
293 | *
294 | * @element ANY
295 | * @param {expression} ngSwipeLeft {@link guide/expression Expression} to evaluate
296 | * upon left swipe. (Event object is available as `$event`)
297 | *
298 | * @example
299 |
300 |
301 |
302 | Some list content, like an email in the inbox
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 | */
311 |
312 | /**
313 | * @ngdoc directive
314 | * @name ngMobile.directive:ngSwipeRight
315 | *
316 | * @description
317 | * Specify custom behavior when an element is swiped to the right on a touchscreen device.
318 | * A rightward swipe is a quick, left-to-right slide of the finger.
319 | * Though ngSwipeRight is designed for touch-based devices, it will work with a mouse click and drag too.
320 | *
321 | * @element ANY
322 | * @param {expression} ngSwipeRight {@link guide/expression Expression} to evaluate
323 | * upon right swipe. (Event object is available as `$event`)
324 | *
325 | * @example
326 |
327 |
328 |
329 | Some list content, like an email in the inbox
330 |