├── .gitignore ├── .htaccess ├── Gruntfile.js ├── README.md ├── app ├── scripts │ ├── app.js │ └── directives │ │ └── typewrite-directive.js └── style │ └── angular-typewrite.css ├── bower.json ├── dist ├── angular-typewrite.css └── angular-typewrite.js ├── favicon.ico ├── index.html ├── npm-dist ├── angular-typewrite.css └── angular-typewrite.js ├── package.json └── robots.txt /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .tmp 3 | .sass-cache 4 | *.swp 5 | bower_components 6 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | # Apache Configuration File 2 | 3 | # (!) Using `.htaccess` files slows down Apache, therefore, if you have access 4 | # to the main server config file (usually called `httpd.conf`), you should add 5 | # this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html. 6 | 7 | # ############################################################################## 8 | # # CROSS-ORIGIN RESOURCE SHARING (CORS) # 9 | # ############################################################################## 10 | 11 | # ------------------------------------------------------------------------------ 12 | # | Cross-domain AJAX requests | 13 | # ------------------------------------------------------------------------------ 14 | 15 | # Enable cross-origin AJAX requests. 16 | # http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity 17 | # http://enable-cors.org/ 18 | 19 | # 20 | # Header set Access-Control-Allow-Origin "*" 21 | # 22 | 23 | # ------------------------------------------------------------------------------ 24 | # | CORS-enabled images | 25 | # ------------------------------------------------------------------------------ 26 | 27 | # Send the CORS header for images when browsers request it. 28 | # https://developer.mozilla.org/en/CORS_Enabled_Image 29 | # http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html 30 | # http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/ 31 | 32 | 33 | 34 | 35 | SetEnvIf Origin ":" IS_CORS 36 | Header set Access-Control-Allow-Origin "*" env=IS_CORS 37 | 38 | 39 | 40 | 41 | # ------------------------------------------------------------------------------ 42 | # | Web fonts access | 43 | # ------------------------------------------------------------------------------ 44 | 45 | # Allow access from all domains for web fonts 46 | 47 | 48 | 49 | Header set Access-Control-Allow-Origin "*" 50 | 51 | 52 | 53 | 54 | # ############################################################################## 55 | # # ERRORS # 56 | # ############################################################################## 57 | 58 | # ------------------------------------------------------------------------------ 59 | # | 404 error prevention for non-existing redirected folders | 60 | # ------------------------------------------------------------------------------ 61 | 62 | # Prevent Apache from returning a 404 error for a rewrite if a directory 63 | # with the same name does not exist. 64 | # http://httpd.apache.org/docs/current/content-negotiation.html#multiviews 65 | # http://www.webmasterworld.com/apache/3808792.htm 66 | 67 | Options -MultiViews 68 | 69 | # ------------------------------------------------------------------------------ 70 | # | Custom error messages / pages | 71 | # ------------------------------------------------------------------------------ 72 | 73 | # You can customize what Apache returns to the client in case of an error (see 74 | # http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.: 75 | 76 | ErrorDocument 404 /404.html 77 | 78 | 79 | # ############################################################################## 80 | # # INTERNET EXPLORER # 81 | # ############################################################################## 82 | 83 | # ------------------------------------------------------------------------------ 84 | # | Better website experience | 85 | # ------------------------------------------------------------------------------ 86 | 87 | # Force IE to render pages in the highest available mode in the various 88 | # cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf. 89 | 90 | 91 | Header set X-UA-Compatible "IE=edge" 92 | # `mod_headers` can't match based on the content-type, however, we only 93 | # want to send this header for HTML pages and not for the other resources 94 | 95 | Header unset X-UA-Compatible 96 | 97 | 98 | 99 | # ------------------------------------------------------------------------------ 100 | # | Cookie setting from iframes | 101 | # ------------------------------------------------------------------------------ 102 | 103 | # Allow cookies to be set from iframes in IE. 104 | 105 | # 106 | # Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"" 107 | # 108 | 109 | # ------------------------------------------------------------------------------ 110 | # | Screen flicker | 111 | # ------------------------------------------------------------------------------ 112 | 113 | # Stop screen flicker in IE on CSS rollovers (this only works in 114 | # combination with the `ExpiresByType` directives for images from below). 115 | 116 | # BrowserMatch "MSIE" brokenvary=1 117 | # BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1 118 | # BrowserMatch "Opera" !brokenvary 119 | # SetEnvIf brokenvary 1 force-no-vary 120 | 121 | 122 | # ############################################################################## 123 | # # MIME TYPES AND ENCODING # 124 | # ############################################################################## 125 | 126 | # ------------------------------------------------------------------------------ 127 | # | Proper MIME types for all files | 128 | # ------------------------------------------------------------------------------ 129 | 130 | 131 | 132 | # Audio 133 | AddType audio/mp4 m4a f4a f4b 134 | AddType audio/ogg oga ogg 135 | 136 | # JavaScript 137 | # Normalize to standard type (it's sniffed in IE anyways): 138 | # http://tools.ietf.org/html/rfc4329#section-7.2 139 | AddType application/javascript js jsonp 140 | AddType application/json json 141 | 142 | # Video 143 | AddType video/mp4 mp4 m4v f4v f4p 144 | AddType video/ogg ogv 145 | AddType video/webm webm 146 | AddType video/x-flv flv 147 | 148 | # Web fonts 149 | AddType application/font-woff woff 150 | AddType application/vnd.ms-fontobject eot 151 | 152 | # Browsers usually ignore the font MIME types and sniff the content, 153 | # however, Chrome shows a warning if other MIME types are used for the 154 | # following fonts. 155 | AddType application/x-font-ttf ttc ttf 156 | AddType font/opentype otf 157 | 158 | # Make SVGZ fonts work on iPad: 159 | # https://twitter.com/FontSquirrel/status/14855840545 160 | AddType image/svg+xml svg svgz 161 | AddEncoding gzip svgz 162 | 163 | # Other 164 | AddType application/octet-stream safariextz 165 | AddType application/x-chrome-extension crx 166 | AddType application/x-opera-extension oex 167 | AddType application/x-shockwave-flash swf 168 | AddType application/x-web-app-manifest+json webapp 169 | AddType application/x-xpinstall xpi 170 | AddType application/xml atom rdf rss xml 171 | AddType image/webp webp 172 | AddType image/x-icon ico 173 | AddType text/cache-manifest appcache manifest 174 | AddType text/vtt vtt 175 | AddType text/x-component htc 176 | AddType text/x-vcard vcf 177 | 178 | 179 | 180 | # ------------------------------------------------------------------------------ 181 | # | UTF-8 encoding | 182 | # ------------------------------------------------------------------------------ 183 | 184 | # Use UTF-8 encoding for anything served as `text/html` or `text/plain`. 185 | AddDefaultCharset utf-8 186 | 187 | # Force UTF-8 for certain file formats. 188 | 189 | AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml 190 | 191 | 192 | 193 | # ############################################################################## 194 | # # URL REWRITES # 195 | # ############################################################################## 196 | 197 | # ------------------------------------------------------------------------------ 198 | # | Rewrite engine | 199 | # ------------------------------------------------------------------------------ 200 | 201 | # Turning on the rewrite engine and enabling the `FollowSymLinks` option is 202 | # necessary for the following directives to work. 203 | 204 | # If your web host doesn't allow the `FollowSymlinks` option, you may need to 205 | # comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the 206 | # performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks 207 | 208 | # Also, some cloud hosting services require `RewriteBase` to be set: 209 | # http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site 210 | 211 | 212 | Options +FollowSymlinks 213 | # Options +SymLinksIfOwnerMatch 214 | RewriteEngine On 215 | # RewriteBase / 216 | 217 | 218 | # ------------------------------------------------------------------------------ 219 | # | Suppressing / Forcing the "www." at the beginning of URLs | 220 | # ------------------------------------------------------------------------------ 221 | 222 | # The same content should never be available under two different URLs especially 223 | # not with and without "www." at the beginning. This can cause SEO problems 224 | # (duplicate content), therefore, you should choose one of the alternatives and 225 | # redirect the other one. 226 | 227 | # By default option 1 (no "www.") is activated: 228 | # http://no-www.org/faq.php?q=class_b 229 | 230 | # If you'd prefer to use option 2, just comment out all the lines from option 1 231 | # and uncomment the ones from option 2. 232 | 233 | # IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME! 234 | 235 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 236 | 237 | # Option 1: rewrite www.example.com → example.com 238 | 239 | 240 | RewriteCond %{HTTPS} !=on 241 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] 242 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] 243 | 244 | 245 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 246 | 247 | # Option 2: rewrite example.com → www.example.com 248 | 249 | # Be aware that the following might not be a good idea if you use "real" 250 | # subdomains for certain parts of your website. 251 | 252 | # 253 | # RewriteCond %{HTTPS} !=on 254 | # RewriteCond %{HTTP_HOST} !^www\..+$ [NC] 255 | # RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] 256 | # 257 | 258 | 259 | # ############################################################################## 260 | # # SECURITY # 261 | # ############################################################################## 262 | 263 | # ------------------------------------------------------------------------------ 264 | # | Content Security Policy (CSP) | 265 | # ------------------------------------------------------------------------------ 266 | 267 | # You can mitigate the risk of cross-site scripting and other content-injection 268 | # attacks by setting a Content Security Policy which whitelists trusted sources 269 | # of content for your site. 270 | 271 | # The example header below allows ONLY scripts that are loaded from the current 272 | # site's origin (no inline scripts, no CDN, etc). This almost certainly won't 273 | # work as-is for your site! 274 | 275 | # To get all the details you'll need to craft a reasonable policy for your site, 276 | # read: http://html5rocks.com/en/tutorials/security/content-security-policy (or 277 | # see the specification: http://w3.org/TR/CSP). 278 | 279 | # 280 | # Header set Content-Security-Policy "script-src 'self'; object-src 'self'" 281 | # 282 | # Header unset Content-Security-Policy 283 | # 284 | # 285 | 286 | # ------------------------------------------------------------------------------ 287 | # | File access | 288 | # ------------------------------------------------------------------------------ 289 | 290 | # Block access to directories without a default document. 291 | # Usually you should leave this uncommented because you shouldn't allow anyone 292 | # to surf through every directory on your server (which may includes rather 293 | # private places like the CMS's directories). 294 | 295 | 296 | Options -Indexes 297 | 298 | 299 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 300 | 301 | # Block access to hidden files and directories. 302 | # This includes directories used by version control systems such as Git and SVN. 303 | 304 | 305 | RewriteCond %{SCRIPT_FILENAME} -d [OR] 306 | RewriteCond %{SCRIPT_FILENAME} -f 307 | RewriteRule "(^|/)\." - [F] 308 | 309 | 310 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 311 | 312 | # Block access to backup and source files. 313 | # These files may be left by some text editors and can pose a great security 314 | # danger when anyone has access to them. 315 | 316 | 317 | Order allow,deny 318 | Deny from all 319 | Satisfy All 320 | 321 | 322 | # ------------------------------------------------------------------------------ 323 | # | Secure Sockets Layer (SSL) | 324 | # ------------------------------------------------------------------------------ 325 | 326 | # Rewrite secure requests properly to prevent SSL certificate warnings, e.g.: 327 | # prevent `https://www.example.com` when your certificate only allows 328 | # `https://secure.example.com`. 329 | 330 | # 331 | # RewriteCond %{SERVER_PORT} !^443 332 | # RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L] 333 | # 334 | 335 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 336 | 337 | # Force client-side SSL redirection. 338 | 339 | # If a user types "example.com" in his browser, the above rule will redirect him 340 | # to the secure version of the site. That still leaves a window of opportunity 341 | # (the initial HTTP connection) for an attacker to downgrade or redirect the 342 | # request. The following header ensures that browser will ONLY connect to your 343 | # server via HTTPS, regardless of what the users type in the address bar. 344 | # http://www.html5rocks.com/en/tutorials/security/transport-layer-security/ 345 | 346 | # 347 | # Header set Strict-Transport-Security max-age=16070400; 348 | # 349 | 350 | # ------------------------------------------------------------------------------ 351 | # | Server software information | 352 | # ------------------------------------------------------------------------------ 353 | 354 | # Avoid displaying the exact Apache version number, the description of the 355 | # generic OS-type and the information about Apache's compiled-in modules. 356 | 357 | # ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`! 358 | 359 | # ServerTokens Prod 360 | 361 | 362 | # ############################################################################## 363 | # # WEB PERFORMANCE # 364 | # ############################################################################## 365 | 366 | # ------------------------------------------------------------------------------ 367 | # | Compression | 368 | # ------------------------------------------------------------------------------ 369 | 370 | 371 | 372 | # Force compression for mangled headers. 373 | # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping 374 | 375 | 376 | SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding 377 | RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding 378 | 379 | 380 | 381 | # Compress all output labeled with one of the following MIME-types 382 | # (for Apache versions below 2.3.7, you don't need to enable `mod_filter` 383 | # and can remove the `` and `` lines 384 | # as `AddOutputFilterByType` is still in the core directives). 385 | 386 | AddOutputFilterByType DEFLATE application/atom+xml \ 387 | application/javascript \ 388 | application/json \ 389 | application/rss+xml \ 390 | application/vnd.ms-fontobject \ 391 | application/x-font-ttf \ 392 | application/x-web-app-manifest+json \ 393 | application/xhtml+xml \ 394 | application/xml \ 395 | font/opentype \ 396 | image/svg+xml \ 397 | image/x-icon \ 398 | text/css \ 399 | text/html \ 400 | text/plain \ 401 | text/x-component \ 402 | text/xml 403 | 404 | 405 | 406 | 407 | # ------------------------------------------------------------------------------ 408 | # | Content transformations | 409 | # ------------------------------------------------------------------------------ 410 | 411 | # Prevent some of the mobile network providers from modifying the content of 412 | # your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5. 413 | 414 | # 415 | # Header set Cache-Control "no-transform" 416 | # 417 | 418 | # ------------------------------------------------------------------------------ 419 | # | ETag removal | 420 | # ------------------------------------------------------------------------------ 421 | 422 | # Since we're sending far-future expires headers (see below), ETags can 423 | # be removed: http://developer.yahoo.com/performance/rules.html#etags. 424 | 425 | # `FileETag None` is not enough for every server. 426 | 427 | Header unset ETag 428 | 429 | 430 | FileETag None 431 | 432 | # ------------------------------------------------------------------------------ 433 | # | Expires headers (for better cache control) | 434 | # ------------------------------------------------------------------------------ 435 | 436 | # The following expires headers are set pretty far in the future. If you don't 437 | # control versioning with filename-based cache busting, consider lowering the 438 | # cache time for resources like CSS and JS to something like 1 week. 439 | 440 | 441 | 442 | ExpiresActive on 443 | ExpiresDefault "access plus 1 month" 444 | 445 | # CSS 446 | ExpiresByType text/css "access plus 1 year" 447 | 448 | # Data interchange 449 | ExpiresByType application/json "access plus 0 seconds" 450 | ExpiresByType application/xml "access plus 0 seconds" 451 | ExpiresByType text/xml "access plus 0 seconds" 452 | 453 | # Favicon (cannot be renamed!) 454 | ExpiresByType image/x-icon "access plus 1 week" 455 | 456 | # HTML components (HTCs) 457 | ExpiresByType text/x-component "access plus 1 month" 458 | 459 | # HTML 460 | ExpiresByType text/html "access plus 0 seconds" 461 | 462 | # JavaScript 463 | ExpiresByType application/javascript "access plus 1 year" 464 | 465 | # Manifest files 466 | ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds" 467 | ExpiresByType text/cache-manifest "access plus 0 seconds" 468 | 469 | # Media 470 | ExpiresByType audio/ogg "access plus 1 month" 471 | ExpiresByType image/gif "access plus 1 month" 472 | ExpiresByType image/jpeg "access plus 1 month" 473 | ExpiresByType image/png "access plus 1 month" 474 | ExpiresByType video/mp4 "access plus 1 month" 475 | ExpiresByType video/ogg "access plus 1 month" 476 | ExpiresByType video/webm "access plus 1 month" 477 | 478 | # Web feeds 479 | ExpiresByType application/atom+xml "access plus 1 hour" 480 | ExpiresByType application/rss+xml "access plus 1 hour" 481 | 482 | # Web fonts 483 | ExpiresByType application/font-woff "access plus 1 month" 484 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month" 485 | ExpiresByType application/x-font-ttf "access plus 1 month" 486 | ExpiresByType font/opentype "access plus 1 month" 487 | ExpiresByType image/svg+xml "access plus 1 month" 488 | 489 | 490 | 491 | # ------------------------------------------------------------------------------ 492 | # | Filename-based cache busting | 493 | # ------------------------------------------------------------------------------ 494 | 495 | # If you're not using a build process to manage your filename version revving, 496 | # you might want to consider enabling the following directives to route all 497 | # requests such as `/css/style.12345.css` to `/css/style.css`. 498 | 499 | # To understand why this is important and a better idea than `*.css?v231`, read: 500 | # http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring 501 | 502 | # 503 | # RewriteCond %{REQUEST_FILENAME} !-f 504 | # RewriteCond %{REQUEST_FILENAME} !-d 505 | # RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L] 506 | # 507 | 508 | # ------------------------------------------------------------------------------ 509 | # | File concatenation | 510 | # ------------------------------------------------------------------------------ 511 | 512 | # Allow concatenation from within specific CSS and JS files, e.g.: 513 | # Inside of `script.combined.js` you could have 514 | # 515 | # 516 | # and they would be included into this single file. 517 | 518 | # 519 | # 520 | # Options +Includes 521 | # AddOutputFilterByType INCLUDES application/javascript application/json 522 | # SetOutputFilter INCLUDES 523 | # 524 | # 525 | # Options +Includes 526 | # AddOutputFilterByType INCLUDES text/css 527 | # SetOutputFilter INCLUDES 528 | # 529 | # 530 | 531 | # ------------------------------------------------------------------------------ 532 | # | Persistent connections | 533 | # ------------------------------------------------------------------------------ 534 | 535 | # Allow multiple requests to be sent over the same TCP connection: 536 | # http://httpd.apache.org/docs/current/en/mod/core.html#keepalive. 537 | 538 | # Enable if you serve a lot of static content but, be aware of the 539 | # possible disadvantages! 540 | 541 | # 542 | # Header set Connection Keep-Alive 543 | # 544 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | // Generated on 2014-10-28 using generator-angular 0.9.8 2 | 'use strict'; 3 | 4 | // # Globbing 5 | // for performance reasons we're only matching one level down: 6 | // 'test/spec/{,*/}*.js' 7 | // use this if you want to recursively match all subfolders: 8 | // 'test/spec/**/*.js' 9 | 10 | var wrapper1 = "(function (root, factory) { 'use strict'; if (typeof define === 'function' && define.amd) { define(['angular'], factory); } else if (typeof module !== 'undefined' && typeof module.exports === 'object') { module.exports = factory(require('angular')); } else { return factory(root.angular); } }(this, function (angular) { 'use strict'; var moduleName = 'angularTypewrite';\n"; 11 | 12 | var wrapper2 = "\nreturn moduleName; }));\n"; 13 | 14 | module.exports = function (grunt) { 15 | 16 | // Load grunt tasks automatically 17 | require('load-grunt-tasks')(grunt); 18 | grunt.loadNpmTasks('grunt-contrib-concat'); 19 | grunt.loadNpmTasks('grunt-remove'); 20 | 21 | 22 | // Time how long tasks take. Can help when optimizing build times 23 | require('time-grunt')(grunt); 24 | 25 | // Configurable paths for the application 26 | var appConfig = { 27 | app: require('./bower.json').appPath || 'app', 28 | dist: 'dist', 29 | npmDist: 'npm-dist' 30 | }; 31 | 32 | // Define the configuration for all the tasks 33 | grunt.initConfig({ 34 | 35 | // Project settings 36 | yeoman: appConfig, 37 | 38 | babel: { 39 | options: { 40 | sourceMap: true, 41 | presets: ['es2015'] 42 | }, 43 | dev: { 44 | files: { 45 | '.tmp/typewrite-module.js': '<%= yeoman.app %>/scripts/app.js', 46 | '.tmp/typewrite-directive.js': '<%= yeoman.app %>/scripts/directives/typewrite-directive.js', 47 | } 48 | } 49 | }, 50 | 51 | // Watches files for changes and runs tasks based on the changed files 52 | watch: { 53 | bower: { 54 | files: ['bower.json'], 55 | tasks: ['wiredep'] 56 | }, 57 | js: { 58 | files: ['<%= yeoman.app %>/scripts/{,*/}*.js'], 59 | tasks: ['newer:jshint:all', 'babel:dev'], 60 | options: { 61 | livereload: '<%= connect.options.livereload %>' 62 | } 63 | }, 64 | jsTest: { 65 | files: ['test/spec/{,*/}*.js'], 66 | tasks: ['newer:jshint:test', 'karma'] 67 | }, 68 | compass: { 69 | files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], 70 | tasks: ['compass:server', 'autoprefixer'] 71 | }, 72 | gruntfile: { 73 | files: ['Gruntfile.js'] 74 | }, 75 | livereload: { 76 | options: { 77 | livereload: '<%= connect.options.livereload %>' 78 | }, 79 | files: [ 80 | '<%= yeoman.app %>/{,*/}*.html', 81 | '.tmp/styles/{,*/}*.css', 82 | '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' 83 | ] 84 | } 85 | }, 86 | 87 | // The actual grunt server settings 88 | connect: { 89 | options: { 90 | port: 9000, 91 | // Change this to '0.0.0.0' to access the server from outside. 92 | hostname: '0.0.0.0', 93 | livereload: 35729 94 | }, 95 | livereload: { 96 | options: { 97 | open: true, 98 | middleware: function (connect) { 99 | return [ 100 | connect.static('.tmp'), 101 | connect().use( 102 | '/bower_components', 103 | connect.static('./bower_components') 104 | ), 105 | connect.static(appConfig.app) 106 | ]; 107 | } 108 | } 109 | }, 110 | test: { 111 | options: { 112 | port: 9001, 113 | middleware: function (connect) { 114 | return [ 115 | connect.static('.tmp'), 116 | connect.static('test'), 117 | connect().use( 118 | '/bower_components', 119 | connect.static('./bower_components') 120 | ), 121 | connect.static(appConfig.app) 122 | ]; 123 | } 124 | } 125 | }, 126 | dist: { 127 | options: { 128 | open: true, 129 | base: '<%= yeoman.dist %>' 130 | } 131 | } 132 | }, 133 | 134 | // Make sure code styles are up to par and there are no obvious mistakes 135 | jshint: { 136 | options: { 137 | jshintrc: '.jshintrc', 138 | reporter: require('jshint-stylish') 139 | }, 140 | all: { 141 | src: [ 142 | 'Gruntfile.js', 143 | '<%= yeoman.app %>/scripts/{,*/}*.js' 144 | ] 145 | }, 146 | test: { 147 | options: { 148 | jshintrc: 'test/.jshintrc' 149 | }, 150 | src: ['test/spec/{,*/}*.js'] 151 | } 152 | }, 153 | 154 | // Empties folders to start fresh 155 | clean: { 156 | dist: { 157 | files: [{ 158 | dot: true, 159 | src: [ 160 | '.tmp', 161 | '<%= yeoman.dist %>/{,*/}*', 162 | '!<%= yeoman.dist %>/.git*' 163 | ] 164 | }] 165 | }, 166 | npmDist: { 167 | files: [{ 168 | dot: true, 169 | src: [ 170 | '.tmp', 171 | '<%= yeoman.npmDist %>/{,*/}*', 172 | '!<%= yeoman.npmDist %>/.git*' 173 | ] 174 | }] 175 | }, 176 | server: '.tmp' 177 | }, 178 | 179 | // Add vendor prefixed styles 180 | autoprefixer: { 181 | options: { 182 | browsers: ['last 5 versions', 'ie 8', 'ie 9'] 183 | }, 184 | dist: { 185 | files: [{ 186 | expand: true, 187 | cwd: '<%= yeoman.app %>/style', 188 | src: '{,*/}*.css', 189 | dest: 'dist/' 190 | }] 191 | } 192 | }, 193 | 194 | // Automatically inject Bower components into the app 195 | wiredep: { 196 | app: { 197 | src: ['<%= yeoman.app %>/index.html'], 198 | ignorePath: /\.\.\// 199 | }, 200 | sass: { 201 | src: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'], 202 | ignorePath: /(\.\.\/){1,2}bower_components\// 203 | } 204 | }, 205 | 206 | // Compiles Sass to CSS and generates necessary files if requested 207 | compass: { 208 | options: { 209 | sassDir: '<%= yeoman.app %>/style', 210 | cssDir: 'dist', 211 | generatedImagesDir: '.tmp/images/generated', 212 | imagesDir: '<%= yeoman.app %>/images', 213 | javascriptsDir: '<%= yeoman.app %>/scripts', 214 | fontsDir: '<%= yeoman.app %>/styles/fonts', 215 | importPath: './bower_components', 216 | httpImagesPath: '/images', 217 | httpGeneratedImagesPath: '/images/generated', 218 | httpFontsPath: '/styles/fonts', 219 | relativeAssets: false, 220 | assetCacheBuster: false, 221 | raw: 'Sass::Script::Number.precision = 10\n' 222 | }, 223 | dist: { 224 | options: { 225 | generatedImagesDir: '<%= yeoman.dist %>/images/generated', 226 | sassDir: '<%= yeoman.app %>/style', 227 | cssDir: 'dist', 228 | } 229 | }, 230 | server: { 231 | options: { 232 | debugInfo: true 233 | } 234 | } 235 | }, 236 | 237 | // Renames files for browser caching purposes 238 | filerev: { 239 | dist: { 240 | src: [ 241 | '<%= yeoman.dist %>/scripts/{,*/}*.js', 242 | '<%= yeoman.dist %>/styles/{,*/}*.css', 243 | '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}', 244 | '<%= yeoman.dist %>/styles/fonts/*' 245 | ] 246 | } 247 | }, 248 | 249 | // Reads HTML for usemin blocks to enable smart builds that automatically 250 | // concat, minify and revision files. Creates configurations in memory so 251 | // additional tasks can operate on them 252 | useminPrepare: { 253 | html: '<%= yeoman.app %>/index.html', 254 | options: { 255 | dest: '<%= yeoman.dist %>', 256 | flow: { 257 | html: { 258 | steps: { 259 | js: ['concat:dist', 'uglifyjs'], 260 | css: ['cssmin'] 261 | }, 262 | post: {} 263 | } 264 | } 265 | } 266 | }, 267 | 268 | // Performs rewrites based on filerev and the useminPrepare configuration 269 | usemin: { 270 | html: ['<%= yeoman.dist %>/{,*/}*.html'], 271 | css: ['<%= yeoman.dist %>/styles/{,*/}*.css'], 272 | options: { 273 | assetsDirs: ['<%= yeoman.dist %>','<%= yeoman.dist %>/images'] 274 | } 275 | }, 276 | 277 | // The following *-min tasks will produce minified files in the dist folder 278 | // By default, your `index.html`'s will take care of 279 | // minification. These next options are pre-configured if you do not wish 280 | // to use the Usemin blocks. 281 | cssmin: { 282 | dist: { 283 | files: { 284 | '<%= yeoman.dist %>/angular-typewrite.css': [ 285 | '<%= yeoman.dist %>/angular-typewrite.css' 286 | ] 287 | } 288 | } 289 | }, 290 | 291 | uglify: { 292 | dist: { 293 | files: { 294 | '<%= yeoman.dist %>/angular-typewrite.js': [ 295 | '<%= yeoman.dist %>/angular-typewrite.js' 296 | ] 297 | } 298 | } 299 | }, 300 | wrap: { 301 | basic: { 302 | src: ['npm-dist/angular-typewrite.js'], 303 | dest: '.', 304 | options: { 305 | wrapper: [wrapper1, wrapper2] 306 | } 307 | } 308 | }, 309 | // Concatenates all extensions files into a single file for distribution 310 | concat: { 311 | dist: { 312 | src: [ 313 | '.tmp/typewrite-module.js', 314 | '.tmp/typewrite-directive.js' 315 | ], 316 | dest: '<%= yeoman.dist %>/angular-typewrite.js' 317 | }, 318 | npmDist: { 319 | src: [ 320 | '.tmp/typewrite-module.js', 321 | '.tmp/typewrite-directive.js' 322 | ], 323 | dest: '<%= yeoman.npmDist %>/angular-typewrite.js' 324 | } 325 | }, 326 | rename: { 327 | main: { 328 | files: [ 329 | {src: ['./npm-dist/app/scripts/directives/typewrite-directive.js'], dest: 'npm-dist/angular-typewrite.js'}, 330 | ] 331 | } 332 | }, 333 | 334 | imagemin: { 335 | dist: { 336 | files: [{ 337 | expand: true, 338 | cwd: '<%= yeoman.app %>/images', 339 | src: '{,*/}*.{png,jpg,jpeg,gif}', 340 | dest: '<%= yeoman.dist %>/images' 341 | }] 342 | } 343 | }, 344 | 345 | svgmin: { 346 | dist: { 347 | files: [{ 348 | expand: true, 349 | cwd: '<%= yeoman.app %>/images', 350 | src: '{,*/}*.svg', 351 | dest: '<%= yeoman.dist %>/images' 352 | }] 353 | } 354 | }, 355 | 356 | htmlmin: { 357 | dist: { 358 | options: { 359 | collapseWhitespace: true, 360 | conservativeCollapse: true, 361 | collapseBooleanAttributes: true, 362 | removeCommentsFromCDATA: true, 363 | removeOptionalTags: true 364 | }, 365 | files: [{ 366 | expand: true, 367 | cwd: '<%= yeoman.dist %>', 368 | src: ['*.html', 'views/{,*/}*.html'], 369 | dest: '<%= yeoman.dist %>' 370 | }] 371 | } 372 | }, 373 | 374 | // ng-annotate tries to make the code safe for minification automatically 375 | // by using the Angular long form for dependency injection. 376 | ngAnnotate: { 377 | dist: { 378 | files: [{ 379 | expand: true, 380 | cwd: '.tmp/concat/scripts', 381 | src: ['*.js', '!oldieshim.js'], 382 | dest: '.tmp/concat/scripts' 383 | }] 384 | } 385 | }, 386 | 387 | // Replace Google CDN references 388 | cdnify: { 389 | dist: { 390 | html: ['<%= yeoman.dist %>/*.html'] 391 | } 392 | }, 393 | 394 | // Copies remaining files to places other tasks can use 395 | copy: { 396 | dist: { 397 | files: [{ 398 | expand: true, 399 | dot: true, 400 | cwd: '<%= yeoman.app %>', 401 | dest: '<%= yeoman.dist %>', 402 | src: [ 403 | '*.{ico,png,txt}', 404 | '.htaccess', 405 | '*.html', 406 | 'views/{,*/}*.html', 407 | 'images/{,*/}*.{webp}', 408 | 'fonts/*' 409 | ] 410 | }, { 411 | expand: true, 412 | cwd: '.tmp/images', 413 | dest: '<%= yeoman.dist %>/images', 414 | src: ['generated/*'] 415 | }] 416 | }, 417 | styles: { 418 | expand: true, 419 | cwd: '<%= yeoman.app %>/styles', 420 | dest: '.tmp/styles/', 421 | src: '{,*/}*.css' 422 | }, 423 | npmDist: { 424 | files: [{ 425 | expand: true, 426 | flatten: true, 427 | dot: true, 428 | cwd: '<%= yeoman.app %>', 429 | dest: '<%= yeoman.npmDist %>', 430 | src: [ 431 | 'style/*' 432 | ] 433 | }] 434 | } 435 | }, 436 | 437 | // Run some tasks in parallel to speed up the build process 438 | concurrent: { 439 | server: [ 440 | 'compass:server' 441 | ], 442 | test: [ 443 | 'compass' 444 | ], 445 | dist: [ 446 | 'compass:dist', 447 | 'imagemin', 448 | 'svgmin' 449 | ] 450 | }, 451 | 452 | // Test settings 453 | karma: { 454 | unit: { 455 | configFile: 'test/karma.conf.js', 456 | singleRun: true 457 | } 458 | }, 459 | 460 | release: { 461 | options: { 462 | additionalFiles: ['bower.json'], 463 | bump: true, //default: true 464 | file: 'package.json', //default: package.json 465 | add: true, //default: true 466 | commit: true, //default: true 467 | tag: true, //default: true 468 | push: true, //default: true 469 | pushTags: true, //default: true 470 | npm: false, //default: true 471 | npmtag: true, //default: no tag 472 | indentation: '\t', //default: ' ' (two spaces) 473 | folder: 'dist', //default project root 474 | beforeBump: [], // optional grunt tasks to run before file versions are bumped 475 | afterBump: [], // optional grunt tasks to run after file versions are bumped 476 | beforeRelease: [], // optional grunt tasks to run after release version is bumped up but before release is packaged 477 | afterRelease: [], // optional grunt tasks to run after release is packaged 478 | updateVars: [], // optional grunt config objects to update (this will update/set the version property on the object specified) 479 | github: { 480 | repo: 'antoniocapelo/angular-typewrite', //put your user/repo here 481 | accessTokenVar: 'GITHUB_ACCESS_TOKEN' //ENVIRONMENT VARIABLE that contains GitHub Access Token 482 | } 483 | } 484 | } 485 | }); 486 | 487 | 488 | grunt.registerTask('serve', 'Compile then start a connect web server', function (target) { 489 | if (target === 'dist') { 490 | return grunt.task.run(['build', 'connect:dist:keepalive']); 491 | } 492 | 493 | grunt.task.run([ 494 | 'clean:server', 495 | 'wiredep', 496 | 'concurrent:server', 497 | 'autoprefixer', 498 | 'connect:livereload', 499 | 'watch' 500 | ]); 501 | }); 502 | 503 | grunt.registerTask('test', [ 504 | 'clean:server', 505 | 'concurrent:test', 506 | 'autoprefixer', 507 | 'connect:test', 508 | 'karma' 509 | ]); 510 | 511 | grunt.registerTask('build', [ 512 | 'clean:dist', 513 | 'autoprefixer', 514 | 'babel', 515 | 'concat:dist', 516 | 'cssmin', 517 | 'uglify' 518 | ]); 519 | 520 | grunt.registerTask('npm-build', [ 521 | 'clean:npmDist', 522 | 'autoprefixer', 523 | 'babel', 524 | 'cssmin', 525 | 'concat:npmDist', 526 | 'wrap', 527 | 'copy:npmDist' 528 | ]); 529 | 530 | grunt.registerTask('default', [ 531 | 'newer:jshint', 532 | 'test', 533 | 'build' 534 | ]); 535 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Angular Typewrite 2 | =================== 3 | 4 | ## Description 5 | AngularJS directive that simulates the effect of typing on a text editor - with a blinking cursor. 6 | 7 | This directive works as an attribute to any HTML element, and it changes the speed/delay of its animation. 8 | 9 | 10 | ##Installation 11 | Just clone this repository, do ``bower install angular-typewrite --save`` or ``npm install angular-typewriter --save``. 12 | After including the **angular-typewrite.js** file on your app, inject it 'angularTypewrite' as a dependency on your AngularJS module: 13 | 14 | angular 15 | .module('yourAppName', [ 16 | 'rest', 17 | 'of', 18 | 'your' 19 | 'dependencies', 20 | 'angularTypewrite']); 21 | 22 | If you installed it by NPM, do: 23 | 24 | var tw = require('angular-typewriter'); 25 | angular 26 | .module('yourAppName', [ 27 | 'rest', 28 | 'of', 29 | 'your' 30 | 'dependencies', 31 | tw]); 32 | 33 | 34 | NOTE: For the blinking effect, add the **angular-typewrite.css** file to your loaded stylesheets. 35 | 36 | ##How to use 37 | 38 | Just add the **typewrite** attribute to the desired HTML element, passing the text that should print as the **'text' attribute** and the directive takes care of the rest. 39 | 40 | The 'text' attribute can be a single string or an array of string. In case an array is passed, the string on each index is erased so the next item can be printed. When the last index is reached, that string stays on the screen. (So if you want to erase the last string, just push an empty string to the end of the array). 41 | 42 | ##Preferences 43 | 44 | These are the optional preferences: 45 | 46 | - **initial delay**: set an 'initial-delay' attribute for the element 47 | - **type delay**: set a 'type-delay' attribute for the element 48 | - **erase delay**: set a 'erase-delay' attribute for the element 49 | - **specify cursor** : set a 'cursor' attribute for the element, specifying which cursor to use 50 | - **turn off cursor blinking**: set the 'blink-cursor' attribute to "false" 51 | - **cursor blinking speed**: set a 'blink-delay' attribute for the element 52 | - **control the typing start with scope variable**: set a 'start' attribute for the element 53 | - **scope callback**: pass the desired scope callback as the 'callback-fn' attribute of the element 54 | 55 | **Note:** Each time/delay value should be set either on seconds (1s) or milisseconds (1000) 56 | 57 | ##Dependencies 58 | This directives only depends on the core AngularJS file and on the CSS file provided (angular-typewrite.css) in order to replicate the cursor blinking effect. 59 | 60 | ## Demo 61 | 62 | I created a [Simple Demo](http://antoniocapelo.github.io/angular-typewrite) to show the directive working. 63 | -------------------------------------------------------------------------------- /app/scripts/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc Wrapper module for the AngularJS Typewrite directive. 3 | * 4 | * @name angularJsTypewriteApp 5 | * @description This directive simulates the effect of typing on a text editor - with a blinking cursor. 6 | * This directive works as an attribute to any HTML element, and it changes the speed/delay of its animation. 7 | * 8 | * # angularJsTypewriteApp 9 | */ 10 | angular 11 | .module('angularTypewrite', []); 12 | -------------------------------------------------------------------------------- /app/scripts/directives/typewrite-directive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AngularJS directive that simulates the effect of typing on a text editor - with a blinking cursor. 3 | * This directive works as an attribute to any HTML element, and it changes the speed/delay of its animation. 4 | * 5 | * There's also a simple less file included for basic styling of the dialog, which can be overridden. 6 | * The config object also lets the user define custom CSS classes for the modal. 7 | * 8 | * How to use: 9 | * 10 | * Just add the desired text to the 'text' attribute of the element and the directive takes care of the rest. 11 | * The 'text' attribute can be a single string or an array of string. In case an array is passed, the string 12 | * on each index is erased so the next item can be printed. When the last index is reached, that string stays 13 | * on the screen. (So if you want to erase the last string, just push an empty string to the end of the array) 14 | * 15 | * These are the optional preferences: 16 | * - initial delay: set an 'initial-delay' attribute for the element 17 | * - type delay: set a 'type-delay' attribute for the element 18 | * - erase delay: set a 'erase-delay' attribute for the element 19 | * - specify cursor : set a 'cursor' attribute for the element, specifying which cursor to use 20 | * - turn off cursor blinking: set the 'blink-cursor' attribute to "false" 21 | * - cursor blinking speed: set a 'blink-delay' attribute for the element 22 | * - scope callback: pass the desired scope callback as the 'callback-fn' attribute of the element 23 | * 24 | * Note: 25 | * Each time/delay value should be set either on seconds (1s) or milliseconds (1000) 26 | * 27 | * Dependencies: 28 | * The directive needs the css file provided in order to replicate the cursor blinking effect. 29 | */ 30 | 31 | 32 | angular 33 | .module('angularTypewrite').directive('typewrite', ['$timeout', function ($timeout) { 34 | function linkFunction($scope, $element, $attrs) { 35 | var timer = null, 36 | initialDelay = $attrs.initialDelay ? getTypeDelay($attrs.initialDelay) : 200, 37 | typeDelay = $attrs.typeDelay || 200, 38 | eraseDelay = $attrs.eraseDelay || typeDelay / 2, 39 | blinkDelay = $attrs.blinkDelay ? getAnimationDelay($attrs.blinkDelay) : false, 40 | cursor = $attrs.cursor || '|', 41 | blinkCursor = typeof $attrs.blinkCursor !== 'undefined' ? $attrs.blinkCursor === 'true' : true, 42 | currentText, 43 | textArray, 44 | running, 45 | auxStyle; 46 | 47 | if ($scope.text) { 48 | if ($scope.text instanceof Array) { 49 | textArray = $scope.text; 50 | currentText = textArray[0]; 51 | } else { 52 | currentText = $scope.text; 53 | } 54 | } 55 | if (typeof $scope.start === 'undefined' || $scope.start) { 56 | typewrite(); 57 | } 58 | 59 | function typewrite() { 60 | timer = $timeout(function () { 61 | updateIt($element, 0, 0, currentText); 62 | }, initialDelay); 63 | } 64 | 65 | function updateIt(element, charIndex, arrIndex, text) { 66 | if (charIndex <= text.length) { 67 | updateValue(element, text.substring(0, charIndex) + cursor); 68 | charIndex++; 69 | timer = $timeout(function () { 70 | updateIt(element, charIndex, arrIndex, text); 71 | }, typeDelay); 72 | return; 73 | } else { 74 | charIndex--; 75 | // check if it's an array 76 | if (textArray && arrIndex < textArray.length - 1) { 77 | timer = $timeout(function () { 78 | cleanAndRestart(element, charIndex, arrIndex, textArray[arrIndex]); 79 | }, initialDelay); 80 | } else { 81 | if ($scope.callbackFn) { 82 | $scope.callbackFn(); 83 | } 84 | blinkIt(element, charIndex, currentText); 85 | } 86 | } 87 | } 88 | 89 | function blinkIt(element, charIndex) { 90 | var text = element.text().substring(0, element.text().length - 1); 91 | if (blinkCursor) { 92 | if (blinkDelay) { 93 | auxStyle = '-webkit-animation:blink-it steps(1) ' + blinkDelay + ' infinite;-moz-animation:blink-it steps(1) ' + blinkDelay + ' infinite ' + 94 | '-ms-animation:blink-it steps(1) ' + blinkDelay + ' infinite;-o-animation:blink-it steps(1) ' + blinkDelay + ' infinite; ' + 95 | 'animation:blink-it steps(1) ' + blinkDelay + ' infinite;'; 96 | updateValue(element, text.substring(0, charIndex) + '' + cursor + ''); 97 | } else { 98 | updateValue(element, text.substring(0, charIndex) + '' + cursor + ''); 99 | } 100 | } else { 101 | updateValue(element, text.substring(0, charIndex)); 102 | } 103 | } 104 | 105 | function cleanAndRestart(element, charIndex, arrIndex, currentText) { 106 | if (charIndex > 0) { 107 | currentText = currentText.slice(0, -1); 108 | // element.html(currentText.substring(0, currentText.length - 1) + cursor); 109 | updateValue(element, currentText + cursor); 110 | charIndex--; 111 | timer = $timeout(function () { 112 | cleanAndRestart(element, charIndex, arrIndex, currentText); 113 | }, eraseDelay); 114 | return; 115 | } else { 116 | arrIndex++; 117 | currentText = textArray[arrIndex]; 118 | timer = $timeout(function () { 119 | updateIt(element, 0, arrIndex, currentText); 120 | }, typeDelay); 121 | } 122 | } 123 | 124 | function getTypeDelay(delay) { 125 | if (typeof delay === 'string') { 126 | return delay.charAt(delay.length - 1) === 's' ? parseInt(delay.substring(0, delay.length - 1), 10) * 1000 : +delay; 127 | } else { 128 | return false; 129 | } 130 | } 131 | 132 | function getAnimationDelay(delay) { 133 | if (typeof delay === 'string') { 134 | return delay.charAt(delay.length - 1) === 's' ? delay : parseInt(delay.substring(0, delay.length - 1), 10) / 1000; 135 | } 136 | } 137 | 138 | function updateValue(element, value) { 139 | if (element.prop('nodeName').toUpperCase() === 'INPUT') { 140 | return element.val(value); 141 | } 142 | return element.html(value); 143 | } 144 | 145 | $scope.$on('$destroy', function () { 146 | if (timer) { 147 | $timeout.cancel(timer); 148 | } 149 | }); 150 | 151 | $scope.$watch('start', function (newVal) { 152 | if (!running && newVal) { 153 | running = !running; 154 | typewrite(); 155 | } 156 | }); 157 | } 158 | 159 | return { 160 | restrict: 'A', 161 | link: linkFunction, 162 | scope: { 163 | text: '=', 164 | callbackFn: '&', 165 | start: '=' 166 | } 167 | }; 168 | 169 | }]); 170 | -------------------------------------------------------------------------------- /app/style/angular-typewrite.css: -------------------------------------------------------------------------------- 1 | @keyframes blink-it { 2 | 0% { 3 | opacity: 1 4 | } 5 | 50% { 6 | opacity: 0 7 | } 8 | 100% { 9 | opacity: 1 10 | } 11 | } 12 | .blink { 13 | animation: blink-it steps(1) 1s infinite 14 | } 15 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-typewrite", 3 | "version": "0.0.14", 4 | "dependencies": { 5 | "angular": "^1.2.0" 6 | }, 7 | "appPath": "app", 8 | "homepage": "https://github.com/antoniocapelo/Angular-Typewrite", 9 | "authors": [ 10 | "antoniocapelo " 11 | ], 12 | "description": "AngularJS directive that simulates the effect of typing on a text editor", 13 | "main": [ 14 | "dist/angular-typewrite.js", 15 | "dist/angular-typewrite.css" 16 | ], 17 | "keywords": [ 18 | "angularjs", 19 | "typewrite", 20 | "command-line", 21 | "typing" 22 | ], 23 | "license": "MIT", 24 | "ignore": [ 25 | "*", 26 | "!dist/", 27 | "!dist/*" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /dist/angular-typewrite.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes blink-it{0%,100%{opacity:1}50%{opacity:0}}@keyframes blink-it{0%,100%{opacity:1}50%{opacity:0}}.blink{-webkit-animation:blink-it steps(1) 1s infinite;animation:blink-it steps(1) 1s infinite} -------------------------------------------------------------------------------- /dist/angular-typewrite.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * @ngdoc Wrapper module for the AngularJS Typewrite directive. 5 | * 6 | * @name angularJsTypewriteApp 7 | * @description This directive simulates the effect of typing on a text editor - with a blinking cursor. 8 | * This directive works as an attribute to any HTML element, and it changes the speed/delay of its animation. 9 | * 10 | * # angularJsTypewriteApp 11 | */ 12 | angular.module('angularTypewrite', []); 13 | //# sourceMappingURL=typewrite-module.js.map 14 | 15 | 'use strict'; 16 | 17 | /** 18 | * AngularJS directive that simulates the effect of typing on a text editor - with a blinking cursor. 19 | * This directive works as an attribute to any HTML element, and it changes the speed/delay of its animation. 20 | * 21 | * There's also a simple less file included for basic styling of the dialog, which can be overridden. 22 | * The config object also lets the user define custom CSS classes for the modal. 23 | * 24 | * How to use: 25 | * 26 | * Just add the desired text to the 'text' attribute of the element and the directive takes care of the rest. 27 | * The 'text' attribute can be a single string or an array of string. In case an array is passed, the string 28 | * on each index is erased so the next item can be printed. When the last index is reached, that string stays 29 | * on the screen. (So if you want to erase the last string, just push an empty string to the end of the array) 30 | * 31 | * These are the optional preferences: 32 | * - initial delay: set an 'initial-delay' attribute for the element 33 | * - type delay: set a 'type-delay' attribute for the element 34 | * - erase delay: set a 'erase-delay' attribute for the element 35 | * - specify cursor : set a 'cursor' attribute for the element, specifying which cursor to use 36 | * - turn off cursor blinking: set the 'blink-cursor' attribute to "false" 37 | * - cursor blinking speed: set a 'blink-delay' attribute for the element 38 | * - scope callback: pass the desired scope callback as the 'callback-fn' attribute of the element 39 | * 40 | * Note: 41 | * Each time/delay value should be set either on seconds (1s) or milliseconds (1000) 42 | * 43 | * Dependencies: 44 | * The directive needs the css file provided in order to replicate the cursor blinking effect. 45 | */ 46 | 47 | angular.module('angularTypewrite').directive('typewrite', ['$timeout', function ($timeout) { 48 | function linkFunction($scope, $element, $attrs) { 49 | var timer = null, 50 | initialDelay = $attrs.initialDelay ? getTypeDelay($attrs.initialDelay) : 200, 51 | typeDelay = $attrs.typeDelay || 200, 52 | eraseDelay = $attrs.eraseDelay || typeDelay / 2, 53 | blinkDelay = $attrs.blinkDelay ? getAnimationDelay($attrs.blinkDelay) : false, 54 | cursor = $attrs.cursor || '|', 55 | blinkCursor = typeof $attrs.blinkCursor !== 'undefined' ? $attrs.blinkCursor === 'true' : true, 56 | currentText, 57 | textArray, 58 | running, 59 | auxStyle; 60 | 61 | if ($scope.text) { 62 | if ($scope.text instanceof Array) { 63 | textArray = $scope.text; 64 | currentText = textArray[0]; 65 | } else { 66 | currentText = $scope.text; 67 | } 68 | } 69 | if (typeof $scope.start === 'undefined' || $scope.start) { 70 | typewrite(); 71 | } 72 | 73 | function typewrite() { 74 | timer = $timeout(function () { 75 | updateIt($element, 0, 0, currentText); 76 | }, initialDelay); 77 | } 78 | 79 | function updateIt(element, charIndex, arrIndex, text) { 80 | if (charIndex <= text.length) { 81 | updateValue(element, text.substring(0, charIndex) + cursor); 82 | charIndex++; 83 | timer = $timeout(function () { 84 | updateIt(element, charIndex, arrIndex, text); 85 | }, typeDelay); 86 | return; 87 | } else { 88 | charIndex--; 89 | // check if it's an array 90 | if (textArray && arrIndex < textArray.length - 1) { 91 | timer = $timeout(function () { 92 | cleanAndRestart(element, charIndex, arrIndex, textArray[arrIndex]); 93 | }, initialDelay); 94 | } else { 95 | if ($scope.callbackFn) { 96 | $scope.callbackFn(); 97 | } 98 | blinkIt(element, charIndex, currentText); 99 | } 100 | } 101 | } 102 | 103 | function blinkIt(element, charIndex) { 104 | var text = element.text().substring(0, element.text().length - 1); 105 | if (blinkCursor) { 106 | if (blinkDelay) { 107 | auxStyle = '-webkit-animation:blink-it steps(1) ' + blinkDelay + ' infinite;-moz-animation:blink-it steps(1) ' + blinkDelay + ' infinite ' + '-ms-animation:blink-it steps(1) ' + blinkDelay + ' infinite;-o-animation:blink-it steps(1) ' + blinkDelay + ' infinite; ' + 'animation:blink-it steps(1) ' + blinkDelay + ' infinite;'; 108 | updateValue(element, text.substring(0, charIndex) + '' + cursor + ''); 109 | } else { 110 | updateValue(element, text.substring(0, charIndex) + '' + cursor + ''); 111 | } 112 | } else { 113 | updateValue(element, text.substring(0, charIndex)); 114 | } 115 | } 116 | 117 | function cleanAndRestart(element, charIndex, arrIndex, currentText) { 118 | if (charIndex > 0) { 119 | currentText = currentText.slice(0, -1); 120 | // element.html(currentText.substring(0, currentText.length - 1) + cursor); 121 | updateValue(element, currentText + cursor); 122 | charIndex--; 123 | timer = $timeout(function () { 124 | cleanAndRestart(element, charIndex, arrIndex, currentText); 125 | }, eraseDelay); 126 | return; 127 | } else { 128 | arrIndex++; 129 | currentText = textArray[arrIndex]; 130 | timer = $timeout(function () { 131 | updateIt(element, 0, arrIndex, currentText); 132 | }, typeDelay); 133 | } 134 | } 135 | 136 | function getTypeDelay(delay) { 137 | if (typeof delay === 'string') { 138 | return delay.charAt(delay.length - 1) === 's' ? parseInt(delay.substring(0, delay.length - 1), 10) * 1000 : +delay; 139 | } else { 140 | return false; 141 | } 142 | } 143 | 144 | function getAnimationDelay(delay) { 145 | if (typeof delay === 'string') { 146 | return delay.charAt(delay.length - 1) === 's' ? delay : parseInt(delay.substring(0, delay.length - 1), 10) / 1000; 147 | } 148 | } 149 | 150 | function updateValue(element, value) { 151 | if (element.prop('nodeName').toUpperCase() === 'INPUT') { 152 | return element.val(value); 153 | } 154 | return element.html(value); 155 | } 156 | 157 | $scope.$on('$destroy', function () { 158 | if (timer) { 159 | $timeout.cancel(timer); 160 | } 161 | }); 162 | 163 | $scope.$watch('start', function (newVal) { 164 | if (!running && newVal) { 165 | running = !running; 166 | typewrite(); 167 | } 168 | }); 169 | } 170 | 171 | return { 172 | restrict: 'A', 173 | link: linkFunction, 174 | scope: { 175 | text: '=', 176 | callbackFn: '&', 177 | start: '=' 178 | } 179 | }; 180 | }]); 181 | //# sourceMappingURL=typewrite-directive.js.map 182 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antoniocapelo/angular-typewrite/864fc99bd2e54007a801a14a095837fc39ae0fb1/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 |
19 |
20 | 23 |
24 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /npm-dist/angular-typewrite.css: -------------------------------------------------------------------------------- 1 | @keyframes blink-it { 2 | 0% { 3 | opacity: 1 4 | } 5 | 50% { 6 | opacity: 0 7 | } 8 | 100% { 9 | opacity: 1 10 | } 11 | } 12 | .blink { 13 | animation: blink-it steps(1) 1s infinite 14 | } 15 | -------------------------------------------------------------------------------- /npm-dist/angular-typewrite.js: -------------------------------------------------------------------------------- 1 | (function (root, factory) { 'use strict'; if (typeof define === 'function' && define.amd) { define(['angular'], factory); } else if (typeof module !== 'undefined' && typeof module.exports === 'object') { module.exports = factory(require('angular')); } else { return factory(root.angular); } }(this, function (angular) { 'use strict'; var moduleName = 'angularTypewrite'; 2 | 3 | 'use strict'; 4 | 5 | /** 6 | * @ngdoc Wrapper module for the AngularJS Typewrite directive. 7 | * 8 | * @name angularJsTypewriteApp 9 | * @description This directive simulates the effect of typing on a text editor - with a blinking cursor. 10 | * This directive works as an attribute to any HTML element, and it changes the speed/delay of its animation. 11 | * 12 | * # angularJsTypewriteApp 13 | */ 14 | angular.module('angularTypewrite', []); 15 | //# sourceMappingURL=typewrite-module.js.map 16 | 17 | 'use strict'; 18 | 19 | /** 20 | * AngularJS directive that simulates the effect of typing on a text editor - with a blinking cursor. 21 | * This directive works as an attribute to any HTML element, and it changes the speed/delay of its animation. 22 | * 23 | * There's also a simple less file included for basic styling of the dialog, which can be overridden. 24 | * The config object also lets the user define custom CSS classes for the modal. 25 | * 26 | * How to use: 27 | * 28 | * Just add the desired text to the 'text' attribute of the element and the directive takes care of the rest. 29 | * The 'text' attribute can be a single string or an array of string. In case an array is passed, the string 30 | * on each index is erased so the next item can be printed. When the last index is reached, that string stays 31 | * on the screen. (So if you want to erase the last string, just push an empty string to the end of the array) 32 | * 33 | * These are the optional preferences: 34 | * - initial delay: set an 'initial-delay' attribute for the element 35 | * - type delay: set a 'type-delay' attribute for the element 36 | * - erase delay: set a 'erase-delay' attribute for the element 37 | * - specify cursor : set a 'cursor' attribute for the element, specifying which cursor to use 38 | * - turn off cursor blinking: set the 'blink-cursor' attribute to "false" 39 | * - cursor blinking speed: set a 'blink-delay' attribute for the element 40 | * - scope callback: pass the desired scope callback as the 'callback-fn' attribute of the element 41 | * 42 | * Note: 43 | * Each time/delay value should be set either on seconds (1s) or milliseconds (1000) 44 | * 45 | * Dependencies: 46 | * The directive needs the css file provided in order to replicate the cursor blinking effect. 47 | */ 48 | 49 | angular.module('angularTypewrite').directive('typewrite', ['$timeout', function ($timeout) { 50 | function linkFunction($scope, $element, $attrs) { 51 | var timer = null, 52 | initialDelay = $attrs.initialDelay ? getTypeDelay($attrs.initialDelay) : 200, 53 | typeDelay = $attrs.typeDelay || 200, 54 | eraseDelay = $attrs.eraseDelay || typeDelay / 2, 55 | blinkDelay = $attrs.blinkDelay ? getAnimationDelay($attrs.blinkDelay) : false, 56 | cursor = $attrs.cursor || '|', 57 | blinkCursor = typeof $attrs.blinkCursor !== 'undefined' ? $attrs.blinkCursor === 'true' : true, 58 | currentText, 59 | textArray, 60 | running, 61 | auxStyle; 62 | 63 | if ($scope.text) { 64 | if ($scope.text instanceof Array) { 65 | textArray = $scope.text; 66 | currentText = textArray[0]; 67 | } else { 68 | currentText = $scope.text; 69 | } 70 | } 71 | if (typeof $scope.start === 'undefined' || $scope.start) { 72 | typewrite(); 73 | } 74 | 75 | function typewrite() { 76 | timer = $timeout(function () { 77 | updateIt($element, 0, 0, currentText); 78 | }, initialDelay); 79 | } 80 | 81 | function updateIt(element, charIndex, arrIndex, text) { 82 | if (charIndex <= text.length) { 83 | updateValue(element, text.substring(0, charIndex) + cursor); 84 | charIndex++; 85 | timer = $timeout(function () { 86 | updateIt(element, charIndex, arrIndex, text); 87 | }, typeDelay); 88 | return; 89 | } else { 90 | charIndex--; 91 | // check if it's an array 92 | if (textArray && arrIndex < textArray.length - 1) { 93 | timer = $timeout(function () { 94 | cleanAndRestart(element, charIndex, arrIndex, textArray[arrIndex]); 95 | }, initialDelay); 96 | } else { 97 | if ($scope.callbackFn) { 98 | $scope.callbackFn(); 99 | } 100 | blinkIt(element, charIndex, currentText); 101 | } 102 | } 103 | } 104 | 105 | function blinkIt(element, charIndex) { 106 | var text = element.text().substring(0, element.text().length - 1); 107 | if (blinkCursor) { 108 | if (blinkDelay) { 109 | auxStyle = '-webkit-animation:blink-it steps(1) ' + blinkDelay + ' infinite;-moz-animation:blink-it steps(1) ' + blinkDelay + ' infinite ' + '-ms-animation:blink-it steps(1) ' + blinkDelay + ' infinite;-o-animation:blink-it steps(1) ' + blinkDelay + ' infinite; ' + 'animation:blink-it steps(1) ' + blinkDelay + ' infinite;'; 110 | updateValue(element, text.substring(0, charIndex) + '' + cursor + ''); 111 | } else { 112 | updateValue(element, text.substring(0, charIndex) + '' + cursor + ''); 113 | } 114 | } else { 115 | updateValue(element, text.substring(0, charIndex)); 116 | } 117 | } 118 | 119 | function cleanAndRestart(element, charIndex, arrIndex, currentText) { 120 | if (charIndex > 0) { 121 | currentText = currentText.slice(0, -1); 122 | // element.html(currentText.substring(0, currentText.length - 1) + cursor); 123 | updateValue(element, currentText + cursor); 124 | charIndex--; 125 | timer = $timeout(function () { 126 | cleanAndRestart(element, charIndex, arrIndex, currentText); 127 | }, eraseDelay); 128 | return; 129 | } else { 130 | arrIndex++; 131 | currentText = textArray[arrIndex]; 132 | timer = $timeout(function () { 133 | updateIt(element, 0, arrIndex, currentText); 134 | }, typeDelay); 135 | } 136 | } 137 | 138 | function getTypeDelay(delay) { 139 | if (typeof delay === 'string') { 140 | return delay.charAt(delay.length - 1) === 's' ? parseInt(delay.substring(0, delay.length - 1), 10) * 1000 : +delay; 141 | } else { 142 | return false; 143 | } 144 | } 145 | 146 | function getAnimationDelay(delay) { 147 | if (typeof delay === 'string') { 148 | return delay.charAt(delay.length - 1) === 's' ? delay : parseInt(delay.substring(0, delay.length - 1), 10) / 1000; 149 | } 150 | } 151 | 152 | function updateValue(element, value) { 153 | if (element.prop('nodeName').toUpperCase() === 'INPUT') { 154 | return element.val(value); 155 | } 156 | return element.html(value); 157 | } 158 | 159 | $scope.$on('$destroy', function () { 160 | if (timer) { 161 | $timeout.cancel(timer); 162 | } 163 | }); 164 | 165 | $scope.$watch('start', function (newVal) { 166 | if (!running && newVal) { 167 | running = !running; 168 | typewrite(); 169 | } 170 | }); 171 | } 172 | 173 | return { 174 | restrict: 'A', 175 | link: linkFunction, 176 | scope: { 177 | text: '=', 178 | callbackFn: '&', 179 | start: '=' 180 | } 181 | }; 182 | }]); 183 | //# sourceMappingURL=typewrite-directive.js.map 184 | 185 | 186 | return moduleName; })); 187 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-typewriter", 3 | "version": "0.0.15", 4 | "description": "AngularJS directive that simulates the effect of typing on a text editor", 5 | "main": "./npm-dist/angular-typewrite.js", 6 | "dependencies": { 7 | "angular": "^1.2.0" 8 | }, 9 | "devDependencies": { 10 | "babel-preset-es2015": "^6.6.0", 11 | "grunt": "~0.4.5", 12 | "grunt-autoprefixer": "^3.0.4", 13 | "grunt-babel": "^6.0.0", 14 | "grunt-compass": "^0.3.9", 15 | "grunt-concurrent": "^2.3.0", 16 | "grunt-connect": "^0.2.0", 17 | "grunt-contrib-clean": "^1.0.0", 18 | "grunt-contrib-concat": "^1.0.1", 19 | "grunt-contrib-copy": "^1.0.0", 20 | "grunt-contrib-cssmin": "^1.0.1", 21 | "grunt-contrib-rename": "0.0.3", 22 | "grunt-contrib-uglify": "^1.0.1", 23 | "grunt-contrib-watch": "^1.0.0", 24 | "grunt-release": "^0.13.1", 25 | "grunt-rename": "^0.1.4", 26 | "grunt-wiredep": "^3.0.1", 27 | "grunt-wrap": "^0.3.0", 28 | "jshint-stylish": "^2.2.0", 29 | "load-grunt-tasks": "^3.5.0", 30 | "time-grunt": "^1.3.0", 31 | "wiredep": "^4.0.0" 32 | }, 33 | "scripts": { 34 | "test": "test", 35 | "buildNPM" : "grunt npm-build && rm -rf ./npm-dist/app " 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "git+https://github.com/antoniocapelo/angular-typewrite.git" 40 | }, 41 | "keywords": [ 42 | "typewriter", 43 | "directive", 44 | "type", 45 | "effect", 46 | "angularjs" 47 | ], 48 | "author": "antoniocapelo", 49 | "license": "ISC", 50 | "bugs": { 51 | "url": "https://github.com/antoniocapelo/angular-typewrite/issues" 52 | }, 53 | "homepage": "https://github.com/antoniocapelo/angular-typewrite#readme" 54 | } 55 | -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | --------------------------------------------------------------------------------