├── .bowerrc ├── www ├── css │ └── style.css ├── img │ └── ionic.png ├── lib │ └── ionic │ │ ├── fonts │ │ ├── ionicons.eot │ │ ├── ionicons.ttf │ │ └── ionicons.woff │ │ ├── version.json │ │ ├── scss │ │ ├── _progress.scss │ │ ├── ionicons │ │ │ ├── ionicons.scss │ │ │ ├── _ionicons-font.scss │ │ │ └── _ionicons-animation.scss │ │ ├── _backdrop.scss │ │ ├── ionic.scss │ │ ├── _loading.scss │ │ ├── _slide-box.scss │ │ ├── _button-bar.scss │ │ ├── _menu.scss │ │ ├── _animations.scss │ │ ├── _radio.scss │ │ ├── _badge.scss │ │ ├── _platform.scss │ │ ├── _action-sheet.scss │ │ ├── _modal.scss │ │ ├── _popup.scss │ │ ├── _list.scss │ │ ├── _select.scss │ │ ├── _range.scss │ │ ├── _grid.scss │ │ ├── _type.scss │ │ ├── _popover.scss │ │ ├── _transitions.scss │ │ ├── _toggle.scss │ │ ├── _util.scss │ │ ├── _checkbox.scss │ │ ├── _form.scss │ │ ├── _button.scss │ │ ├── _reset.scss │ │ ├── _scaffolding.scss │ │ ├── _bar.scss │ │ ├── _tabs.scss │ │ └── _mixins.scss │ │ └── js │ │ └── angular │ │ ├── angular-resource.min.js │ │ ├── angular-sanitize.min.js │ │ └── angular-animate.min.js ├── templates │ ├── categories.html │ ├── password_view.html │ ├── password_list.html │ ├── locked.html │ ├── create_vault.html │ ├── firebase.html │ └── password_new.html ├── index.html └── js │ ├── app.js │ └── angularfire.min.js ├── ionic.project ├── bower.json ├── .gitignore ├── CHANGELOG.md ├── package.json ├── hooks ├── before_platform_add │ └── init_directories.js ├── after_plugin_rm │ └── 010_deregister_plugin.js ├── after_platform_add │ └── 010_install_plugins.js ├── after_prepare │ ├── 020_remove_sass_from_platforms.js │ └── 010_add_platform_class.js ├── after_plugin_add │ └── 010_register_plugin.js └── README.md ├── config.xml ├── scss └── ionic.app.scss ├── LICENSE ├── gulpfile.js └── README.md /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "www/lib" 3 | } 4 | -------------------------------------------------------------------------------- /www/css/style.css: -------------------------------------------------------------------------------- 1 | /* Empty. Add your own CSS if you like */ 2 | -------------------------------------------------------------------------------- /ionic.project: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cipher Safe", 3 | "app_id": "" 4 | } 5 | -------------------------------------------------------------------------------- /www/img/ionic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nraboy/ionic-cipher-safe-app/HEAD/www/img/ionic.png -------------------------------------------------------------------------------- /www/lib/ionic/fonts/ionicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nraboy/ionic-cipher-safe-app/HEAD/www/lib/ionic/fonts/ionicons.eot -------------------------------------------------------------------------------- /www/lib/ionic/fonts/ionicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nraboy/ionic-cipher-safe-app/HEAD/www/lib/ionic/fonts/ionicons.ttf -------------------------------------------------------------------------------- /www/lib/ionic/fonts/ionicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nraboy/ionic-cipher-safe-app/HEAD/www/lib/ionic/fonts/ionicons.woff -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CipherSafe", 3 | "private": "true", 4 | "devDependencies": { 5 | "ionic": "driftyco/ionic-bower#1.0.0-beta.14" 6 | } 7 | } -------------------------------------------------------------------------------- /www/lib/ionic/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0-beta.14", 3 | "codename": "magnesium-mongoose", 4 | "date": "2014-12-15", 5 | "time": "20:15:38" 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Specifies intentionally untracked files to ignore when using Git 2 | # http://git-scm.com/docs/gitignore 3 | 4 | node_modules/ 5 | platforms/ 6 | plugins/ 7 | 8 | .DS_Store* 9 | ~* 10 | Thumbs.db* 11 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_progress.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Progress 4 | * -------------------------------------------------- 5 | */ 6 | 7 | progress { 8 | display: block; 9 | margin: $progress-margin; 10 | width: $progress-width; 11 | } 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.0.2 - March 6, 2015 2 | 3 | * Update AngularFire to 1.0.0 4 | * Update Firebase to 2.2.2 5 | * Update all code to reflect new versions of AngularFire and Firebase 6 | 7 | 0.0.1 - March 4, 2015 8 | 9 | * Initial release to GitHub as part of the AirPair 100K Competition 10 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/ionicons/ionicons.scss: -------------------------------------------------------------------------------- 1 | @import "ionicons-variables"; 2 | /*! 3 | Ionicons, v1.5.2 4 | Created by Ben Sperry for the Ionic Framework, http://ionicons.com/ 5 | https://twitter.com/benjsperry https://twitter.com/ionicframework 6 | MIT License: https://github.com/driftyco/ionicons 7 | */ 8 | 9 | @import "ionicons-font"; 10 | @import "ionicons-animation"; 11 | @import "ionicons-icons"; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ciphersafe", 3 | "version": "1.0.0", 4 | "description": "Cipher Safe: An Ionic project", 5 | "dependencies": { 6 | "gulp": "^3.5.6", 7 | "gulp-concat": "^2.2.0", 8 | "gulp-minify-css": "^0.3.0", 9 | "gulp-rename": "^1.2.0", 10 | "gulp-sass": "^0.7.1", 11 | "jshint": "^2.6.0" 12 | }, 13 | "devDependencies": { 14 | "bower": "^1.3.3", 15 | "gulp-util": "^2.2.14", 16 | "shelljs": "^0.3.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_backdrop.scss: -------------------------------------------------------------------------------- 1 | 2 | .backdrop { 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | z-index: $z-index-backdrop; 7 | 8 | width: 100%; 9 | height: 100%; 10 | 11 | background-color: $loading-backdrop-bg-color; 12 | 13 | visibility: hidden; 14 | opacity: 0; 15 | 16 | &.visible { 17 | visibility: visible; 18 | } 19 | &.active { 20 | opacity: 1; 21 | } 22 | 23 | @include transition($loading-backdrop-fadein-duration opacity linear); 24 | } 25 | -------------------------------------------------------------------------------- /www/templates/categories.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{item.category}} 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /www/templates/password_view.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | {{password.title}} 9 | 10 | 11 | {{password.username}} 12 | 13 | 14 | {{password.password}} 15 | 16 |
17 |
18 |
19 | -------------------------------------------------------------------------------- /hooks/before_platform_add/init_directories.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * On a fresh clone, the local platforms/ and plugins/ directories will be 5 | * missing, so ensure they get created before the first platform is added. 6 | */ 7 | var fs = require('fs'); 8 | var path = require('path'); 9 | 10 | var platformsDir = path.resolve(__dirname, '../../platforms'); 11 | var pluginsDir = path.resolve(__dirname, '../../plugins'); 12 | 13 | try { 14 | fs.mkdirSync(platformsDir, function (err) { 15 | if (err) { console.error(err); } 16 | }); 17 | } catch(ex) {} 18 | 19 | try { 20 | fs.mkdirSync(pluginsDir, function (err) { 21 | if (err) { console.error(err); } 22 | }); 23 | } catch(ex) {} 24 | -------------------------------------------------------------------------------- /www/templates/password_list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{item.password.title}} 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/ionic.scss: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | @import 4 | // Ionicons 5 | "ionicons/ionicons.scss", 6 | 7 | // Variables 8 | "mixins", 9 | "variables", 10 | 11 | // Base 12 | "reset", 13 | "scaffolding", 14 | "type", 15 | 16 | // Components 17 | "action-sheet", 18 | "backdrop", 19 | "bar", 20 | "tabs", 21 | "menu", 22 | "modal", 23 | "popover", 24 | "popup", 25 | "loading", 26 | "items", 27 | "list", 28 | "badge", 29 | "slide-box", 30 | 31 | // Forms 32 | "form", 33 | "checkbox", 34 | "toggle", 35 | "radio", 36 | "range", 37 | "select", 38 | "progress", 39 | 40 | // Buttons 41 | "button", 42 | "button-bar", 43 | 44 | // Util 45 | "grid", 46 | "util", 47 | "platform", 48 | 49 | // Animations 50 | "animations", 51 | "transitions"; 52 | -------------------------------------------------------------------------------- /www/templates/locked.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

Welcome

5 |
6 |
7 |
8 | 11 |
12 |
13 |
14 | Unlock 15 | Reset 16 |
17 |
18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Cipher Safe 4 | 5 | Store passwords on Firebase using a master password and AES strength encryption 6 | 7 | 8 | Nic Raboy 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Cipher Safe 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /scss/ionic.app.scss: -------------------------------------------------------------------------------- 1 | /* 2 | To customize the look and feel of Ionic, you can override the variables 3 | in ionic's _variables.scss file. 4 | 5 | For example, you might change some of the default colors: 6 | 7 | $light: #fff !default; 8 | $stable: #f8f8f8 !default; 9 | $positive: #387ef5 !default; 10 | $calm: #11c1f3 !default; 11 | $balanced: #33cd5f !default; 12 | $energized: #ffc900 !default; 13 | $assertive: #ef473a !default; 14 | $royal: #886aea !default; 15 | $dark: #444 !default; 16 | */ 17 | 18 | // The path for our ionicons font files, relative to the built CSS in www/css 19 | $ionicons-font-path: "../lib/ionic/fonts" !default; 20 | 21 | // Include all of Ionic 22 | @import "www/lib/ionic/scss/ionic"; 23 | 24 | -------------------------------------------------------------------------------- /www/templates/create_vault.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |

Welcome

5 |
6 |
7 |
8 | 11 | 14 |
15 |
16 |
17 | Create 18 |
19 |
20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /hooks/after_plugin_rm/010_deregister_plugin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Remove plugins from cordovaPlugins array after_plugin_rm 5 | */ 6 | var fs = require('fs'); 7 | var packageJSON = require('../../package.json'); 8 | 9 | packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || []; 10 | 11 | process.env.CORDOVA_PLUGINS.split(',').forEach(function (plugin) { 12 | var index = packageJSON.cordovaPlugins.indexOf(plugin); 13 | if (index > -1) { 14 | packageJSON.cordovaPlugins.splice(index, 1); 15 | } else { 16 | //If it didnt find a match, it may be listed as {id,locator} 17 | for(var i = 0, j = packageJSON.cordovaPlugins.length; i < j; i++) { 18 | var packagePlugin = packageJSON.cordovaPlugins[i]; 19 | if(typeof packagePlugin == 'object' && packagePlugin.id == plugin) { 20 | packageJSON.cordovaPlugins.splice(index, 1); 21 | break; 22 | } 23 | } 24 | } 25 | }); 26 | 27 | fs.writeFile('package.json', JSON.stringify(packageJSON, null, 2)); 28 | -------------------------------------------------------------------------------- /www/templates/firebase.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 | 8 | 11 |
12 |
13 |
14 | Login 15 | Register 16 |
17 |

18 | If the account does not exist, it will be created 19 |

20 |
21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/ionicons/_ionicons-font.scss: -------------------------------------------------------------------------------- 1 | // Ionicons Font Path 2 | // -------------------------- 3 | 4 | @font-face { 5 | font-family: $ionicons-font-family; 6 | src:url("#{$ionicons-font-path}/ionicons.eot?v=#{$ionicons-version}"); 7 | src:url("#{$ionicons-font-path}/ionicons.eot?v=#{$ionicons-version}#iefix") format("embedded-opentype"), 8 | url("#{$ionicons-font-path}/ionicons.ttf?v=#{$ionicons-version}") format("truetype"), 9 | url("#{$ionicons-font-path}/ionicons.woff?v=#{$ionicons-version}") format("woff"), 10 | url("#{$ionicons-font-path}/ionicons.svg?v=#{$ionicons-version}#Ionicons") format("svg"); 11 | font-weight: normal; 12 | font-style: normal; 13 | } 14 | 15 | .ion { 16 | display: inline-block; 17 | font-family: $ionicons-font-family; 18 | speak: none; 19 | font-style: normal; 20 | font-weight: normal; 21 | font-variant: normal; 22 | text-transform: none; 23 | text-rendering: auto; 24 | line-height: 1; 25 | -webkit-font-smoothing: antialiased; 26 | -moz-osx-font-smoothing: grayscale; 27 | } -------------------------------------------------------------------------------- /www/lib/ionic/scss/_loading.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Loading 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .loading-container { 8 | position: absolute; 9 | left: 0; 10 | top: 0; 11 | right: 0; 12 | bottom: 0; 13 | 14 | z-index: $z-index-loading; 15 | 16 | @include display-flex(); 17 | @include justify-content(center); 18 | @include align-items(center); 19 | 20 | @include transition(0.2s opacity linear); 21 | visibility: hidden; 22 | opacity: 0; 23 | 24 | &:not(.visible) .icon { 25 | display: none; 26 | } 27 | &.visible { 28 | visibility: visible; 29 | } 30 | &.active { 31 | opacity: 1; 32 | } 33 | 34 | .loading { 35 | padding: $loading-padding; 36 | 37 | border-radius: $loading-border-radius; 38 | background-color: $loading-bg-color; 39 | 40 | color: $loading-text-color; 41 | 42 | text-align: center; 43 | text-overflow: ellipsis; 44 | font-size: $loading-font-size; 45 | 46 | h1, h2, h3, h4, h5, h6 { 47 | color: $loading-text-color; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /hooks/after_platform_add/010_install_plugins.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Install all plugins listed in package.json 5 | * https://raw.githubusercontent.com/diegonetto/generator-ionic/master/templates/hooks/after_platform_add/install_plugins.js 6 | */ 7 | var exec = require('child_process').exec; 8 | var path = require('path'); 9 | var sys = require('sys'); 10 | 11 | var packageJSON = null; 12 | 13 | try { 14 | packageJSON = require('../../package.json'); 15 | } catch(ex) { 16 | console.log('\nThere was an error fetching your package.json file.') 17 | console.log('\nPlease ensure a valid package.json is in the root of this project\n') 18 | return; 19 | } 20 | 21 | var cmd = process.platform === 'win32' ? 'cordova.cmd' : 'cordova'; 22 | // var script = path.resolve(__dirname, '../../node_modules/cordova/bin', cmd); 23 | 24 | packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || []; 25 | packageJSON.cordovaPlugins.forEach(function (plugin) { 26 | exec('cordova plugin add ' + plugin, function (error, stdout, stderr) { 27 | sys.puts(stdout); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /hooks/after_prepare/020_remove_sass_from_platforms.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * After prepare, files are copied to the platforms/ios and platforms/android folders. 5 | * Lets clean up some of those files that arent needed with this hook. 6 | */ 7 | var fs = require('fs'); 8 | var path = require('path'); 9 | 10 | var deleteFolderRecursive = function(removePath) { 11 | if( fs.existsSync(removePath) ) { 12 | fs.readdirSync(removePath).forEach(function(file,index){ 13 | var curPath = path.join(removePath, file); 14 | if(fs.lstatSync(curPath).isDirectory()) { // recurse 15 | deleteFolderRecursive(curPath); 16 | } else { // delete file 17 | fs.unlinkSync(curPath); 18 | } 19 | }); 20 | fs.rmdirSync(removePath); 21 | } 22 | }; 23 | 24 | var iosPlatformsDir = path.resolve(__dirname, '../../platforms/ios/www/lib/ionic/scss'); 25 | var androidPlatformsDir = path.resolve(__dirname, '../../platforms/android/assets/www/lib/ionic/scss'); 26 | 27 | deleteFolderRecursive(iosPlatformsDir); 28 | deleteFolderRecursive(androidPlatformsDir); 29 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_slide-box.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Slide Box 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .slider { 8 | position: relative; 9 | visibility: hidden; 10 | // Make sure items don't scroll over ever 11 | overflow: hidden; 12 | } 13 | 14 | .slider-slides { 15 | position: relative; 16 | height: 100%; 17 | } 18 | 19 | .slider-slide { 20 | position: relative; 21 | display: block; 22 | float: left; 23 | width: 100%; 24 | height: 100%; 25 | vertical-align: top; 26 | } 27 | 28 | .slider-slide-image { 29 | > img { 30 | width: 100%; 31 | } 32 | } 33 | 34 | .slider-pager { 35 | position: absolute; 36 | bottom: 20px; 37 | z-index: $z-index-slider-pager; 38 | width: 100%; 39 | height: 15px; 40 | text-align: center; 41 | 42 | .slider-pager-page { 43 | display: inline-block; 44 | margin: 0px 3px; 45 | width: 15px; 46 | color: #000; 47 | text-decoration: none; 48 | 49 | opacity: 0.3; 50 | 51 | &.active { 52 | @include transition(opacity 0.4s ease-in); 53 | opacity: 1; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_button-bar.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Button Bar 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .button-bar { 8 | @include display-flex(); 9 | @include flex(1); 10 | width: 100%; 11 | 12 | &.button-bar-inline { 13 | display: block; 14 | width: auto; 15 | 16 | @include clearfix(); 17 | 18 | > .button { 19 | width: auto; 20 | display: inline-block; 21 | float: left; 22 | } 23 | } 24 | } 25 | 26 | .button-bar > .button { 27 | @include flex(1); 28 | display: block; 29 | 30 | overflow: hidden; 31 | 32 | padding: 0 16px; 33 | 34 | width: 0; 35 | 36 | border-width: 1px 0px 1px 1px; 37 | border-radius: 0; 38 | text-align: center; 39 | text-overflow: ellipsis; 40 | white-space: nowrap; 41 | 42 | &:before, 43 | .icon:before { 44 | line-height: 44px; 45 | } 46 | 47 | &:first-child { 48 | border-radius: $button-border-radius 0px 0px $button-border-radius; 49 | } 50 | &:last-child { 51 | border-right-width: 1px; 52 | border-radius: 0px $button-border-radius $button-border-radius 0px; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /www/templates/password_new.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | 11 | 14 | 17 |
18 |
19 |
20 | Save 21 |
22 |
23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Nic Raboy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_menu.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Menus 4 | * -------------------------------------------------- 5 | * Side panel structure 6 | */ 7 | 8 | .menu { 9 | position: absolute; 10 | top: 0; 11 | bottom: 0; 12 | z-index: $z-index-menu; 13 | overflow: hidden; 14 | 15 | min-height: 100%; 16 | max-height: 100%; 17 | width: $menu-width; 18 | 19 | background-color: $menu-bg; 20 | 21 | .scroll-content { 22 | z-index: $z-index-menu-scroll-content; 23 | } 24 | 25 | .bar-header { 26 | z-index: $z-index-menu-bar-header; 27 | } 28 | } 29 | 30 | .menu-content { 31 | @include transform(none); 32 | box-shadow: $menu-side-shadow; 33 | } 34 | 35 | .menu-open .menu-content .pane, 36 | .menu-open .menu-content .scroll-content { 37 | pointer-events: none; 38 | } 39 | 40 | .grade-b .menu-content, 41 | .grade-c .menu-content { 42 | @include box-sizing(content-box); 43 | right: -1px; 44 | left: -1px; 45 | border-right: 1px solid #ccc; 46 | border-left: 1px solid #ccc; 47 | box-shadow: none; 48 | } 49 | 50 | .menu-left { 51 | left: 0; 52 | } 53 | 54 | .menu-right { 55 | right: 0; 56 | } 57 | 58 | .aside-open.aside-resizing .menu-right { 59 | display: none; 60 | } 61 | 62 | .menu-animated { 63 | @include transition-transform($menu-animation-speed ease); 64 | } 65 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_animations.scss: -------------------------------------------------------------------------------- 1 | 2 | // Slide up from the bottom, used for modals 3 | // ------------------------------- 4 | 5 | .slide-in-up { 6 | @include translate3d(0, 100%, 0); 7 | } 8 | .slide-in-up.ng-enter, 9 | .slide-in-up > .ng-enter { 10 | @include transition(all cubic-bezier(.1, .7, .1, 1) 400ms); 11 | } 12 | .slide-in-up.ng-enter-active, 13 | .slide-in-up > .ng-enter-active { 14 | @include translate3d(0, 0, 0); 15 | } 16 | 17 | .slide-in-up.ng-leave, 18 | .slide-in-up > .ng-leave { 19 | @include transition(all ease-in-out 250ms); 20 | } 21 | 22 | 23 | // Scale Out 24 | // Scale from hero (1 in this case) to zero 25 | // ------------------------------- 26 | 27 | @-webkit-keyframes scaleOut { 28 | from { -webkit-transform: scale(1); opacity: 1; } 29 | to { -webkit-transform: scale(0.8); opacity: 0; } 30 | } 31 | @keyframes scaleOut { 32 | from { transform: scale(1); opacity: 1; } 33 | to { transform: scale(0.8); opacity: 0; } 34 | } 35 | 36 | 37 | // Super Scale In 38 | // Scale from super (1.x) to duper (1 in this case) 39 | // ------------------------------- 40 | 41 | @-webkit-keyframes superScaleIn { 42 | from { -webkit-transform: scale(1.2); opacity: 0; } 43 | to { -webkit-transform: scale(1); opacity: 1 } 44 | } 45 | @keyframes superScaleIn { 46 | from { transform: scale(1.2); opacity: 0; } 47 | to { transform: scale(1); opacity: 1; } 48 | } 49 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_radio.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Radio Button Inputs 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .item-radio { 8 | padding: 0; 9 | 10 | &:hover { 11 | cursor: pointer; 12 | } 13 | } 14 | 15 | .item-radio .item-content { 16 | /* give some room to the right for the checkmark icon */ 17 | padding-right: $item-padding * 4; 18 | } 19 | 20 | .item-radio .radio-icon { 21 | /* checkmark icon will be hidden by default */ 22 | position: absolute; 23 | top: 0; 24 | right: 0; 25 | z-index: $z-index-item-radio; 26 | visibility: hidden; 27 | padding: $item-padding - 2; 28 | height: 100%; 29 | font-size: 24px; 30 | } 31 | 32 | .item-radio input { 33 | /* hide any radio button inputs elements (the ugly circles) */ 34 | position: absolute; 35 | left: -9999px; 36 | 37 | &:checked ~ .item-content { 38 | /* style the item content when its checked */ 39 | background: #f7f7f7; 40 | } 41 | 42 | &:checked ~ .radio-icon { 43 | /* show the checkmark icon when its checked */ 44 | visibility: visible; 45 | } 46 | } 47 | 48 | // Hack for Android to correctly display the checked item 49 | // http://timpietrusky.com/advanced-checkbox-hack 50 | .platform-android.grade-b .item-radio, 51 | .platform-android.grade-c .item-radio { 52 | -webkit-animation: androidCheckedbugfix infinite 1s; 53 | } 54 | @-webkit-keyframes androidCheckedbugfix { 55 | from { padding: 0; } 56 | to { padding: 0; } 57 | } 58 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var gutil = require('gulp-util'); 3 | var bower = require('bower'); 4 | var concat = require('gulp-concat'); 5 | var sass = require('gulp-sass'); 6 | var minifyCss = require('gulp-minify-css'); 7 | var rename = require('gulp-rename'); 8 | var sh = require('shelljs'); 9 | 10 | var paths = { 11 | sass: ['./scss/**/*.scss'] 12 | }; 13 | 14 | gulp.task('default', ['sass']); 15 | 16 | gulp.task('sass', function(done) { 17 | gulp.src('./scss/ionic.app.scss') 18 | .pipe(sass()) 19 | .pipe(gulp.dest('./www/css/')) 20 | .pipe(minifyCss({ 21 | keepSpecialComments: 0 22 | })) 23 | .pipe(rename({ extname: '.min.css' })) 24 | .pipe(gulp.dest('./www/css/')) 25 | .on('end', done); 26 | }); 27 | 28 | gulp.task('watch', function() { 29 | gulp.watch(paths.sass, ['sass']); 30 | }); 31 | 32 | gulp.task('install', ['git-check'], function() { 33 | return bower.commands.install() 34 | .on('log', function(data) { 35 | gutil.log('bower', gutil.colors.cyan(data.id), data.message); 36 | }); 37 | }); 38 | 39 | gulp.task('git-check', function(done) { 40 | if (!sh.which('git')) { 41 | console.log( 42 | ' ' + gutil.colors.red('Git is not installed.'), 43 | '\n Git, the version control system, is required to download Ionic.', 44 | '\n Download git here:', gutil.colors.cyan('http://git-scm.com/downloads') + '.', 45 | '\n Once git is installed, run \'' + gutil.colors.cyan('gulp install') + '\' again.' 46 | ); 47 | process.exit(1); 48 | } 49 | done(); 50 | }); 51 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_badge.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Badges 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .badge { 8 | @include badge-style($badge-default-bg, $badge-default-text); 9 | z-index: $z-index-badge; 10 | display: inline-block; 11 | padding: 3px 8px; 12 | min-width: 10px; 13 | border-radius: $badge-border-radius; 14 | vertical-align: baseline; 15 | text-align: center; 16 | white-space: nowrap; 17 | font-weight: $badge-font-weight; 18 | font-size: $badge-font-size; 19 | line-height: $badge-line-height; 20 | 21 | &:empty { 22 | display: none; 23 | } 24 | } 25 | 26 | //Be sure to override specificity of rule that 'badge color matches tab color by default' 27 | .tabs .tab-item .badge, 28 | .badge { 29 | &.badge-light { 30 | @include badge-style($badge-light-bg, $badge-light-text); 31 | } 32 | &.badge-stable { 33 | @include badge-style($badge-stable-bg, $badge-stable-text); 34 | } 35 | &.badge-positive { 36 | @include badge-style($badge-positive-bg, $badge-positive-text); 37 | } 38 | &.badge-calm { 39 | @include badge-style($badge-calm-bg, $badge-calm-text); 40 | } 41 | &.badge-assertive { 42 | @include badge-style($badge-assertive-bg, $badge-assertive-text); 43 | } 44 | &.badge-balanced { 45 | @include badge-style($badge-balanced-bg, $badge-balanced-text); 46 | } 47 | &.badge-energized { 48 | @include badge-style($badge-energized-bg, $badge-energized-text); 49 | } 50 | &.badge-royal { 51 | @include badge-style($badge-royal-bg, $badge-royal-text); 52 | } 53 | &.badge-dark { 54 | @include badge-style($badge-dark-bg, $badge-dark-text); 55 | } 56 | } 57 | 58 | // Quick fix for labels/badges in buttons 59 | .button .badge { 60 | position: relative; 61 | top: -1px; 62 | } 63 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_platform.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Platform 4 | * -------------------------------------------------- 5 | * Platform specific tweaks 6 | */ 7 | 8 | .platform-ios.platform-cordova { 9 | // iOS7/8 has a status bar which sits on top of the header. 10 | // Bump down everything to make room for it. However, if 11 | // if its in Cordova, and set to fullscreen, then disregard the bump. 12 | &:not(.fullscreen) { 13 | .bar-header:not(.bar-subheader) { 14 | height: $bar-height + $ios-statusbar-height; 15 | 16 | &.item-input-inset .item-input-wrapper { 17 | margin-top: 19px !important; 18 | } 19 | 20 | > * { 21 | margin-top: $ios-statusbar-height; 22 | } 23 | } 24 | .tabs-top > .tabs, 25 | .tabs.tabs-top { 26 | top: $bar-height + $ios-statusbar-height; 27 | } 28 | 29 | .has-header, 30 | .bar-subheader { 31 | top: $bar-height + $ios-statusbar-height; 32 | } 33 | .has-subheader { 34 | top: $bar-height + $bar-subheader-height + $ios-statusbar-height; 35 | } 36 | .has-tabs-top { 37 | top: $bar-height + $tabs-height + $ios-statusbar-height; 38 | } 39 | .has-header.has-subheader.has-tabs-top { 40 | top: $bar-height + $bar-subheader-height + $tabs-height + $ios-statusbar-height; 41 | } 42 | } 43 | &.status-bar-hide { 44 | // Cordova doesn't adjust the body height correctly, this makes up for it 45 | margin-bottom: 20px; 46 | } 47 | } 48 | 49 | @media (orientation:landscape) { 50 | .platform-ios.platform-browser.platform-ipad { 51 | position: fixed; // required for iPad 7 Safari 52 | } 53 | } 54 | 55 | .platform-c:not(.enable-transitions) * { 56 | // disable transitions on grade-c devices (Android 2) 57 | -webkit-transition: none !important; 58 | transition: none !important; 59 | } 60 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_action-sheet.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Action Sheets 3 | * -------------------------------------------------- 4 | */ 5 | 6 | .action-sheet-backdrop { 7 | @include transition(background-color 300ms ease-in-out); 8 | position: fixed; 9 | top: 0; 10 | left: 0; 11 | z-index: $z-index-action-sheet; 12 | width: 100%; 13 | height: 100%; 14 | background-color: rgba(0,0,0,0); 15 | 16 | &.active { 17 | background-color: rgba(0,0,0,0.5); 18 | } 19 | } 20 | 21 | .action-sheet-wrapper { 22 | @include translate3d(0, 100%, 0); 23 | @include transition(all ease-in-out 300ms); 24 | position: absolute; 25 | bottom: 0; 26 | width: 100%; 27 | } 28 | 29 | .action-sheet-up { 30 | @include translate3d(0, 0, 0); 31 | } 32 | 33 | .action-sheet { 34 | margin-left: 15px; 35 | margin-right: 15px; 36 | width: auto; 37 | z-index: $z-index-action-sheet; 38 | overflow: hidden; 39 | 40 | .button { 41 | display: block; 42 | padding: 1px; 43 | width: 100%; 44 | border-radius: 0; 45 | 46 | background-color: transparent; 47 | 48 | color: $positive; 49 | font-size: 18px; 50 | 51 | &.destructive { 52 | color: $assertive; 53 | } 54 | } 55 | } 56 | 57 | .action-sheet-title { 58 | padding: 10px; 59 | color: lighten($base-color, 40%); 60 | text-align: center; 61 | font-size: 12px; 62 | } 63 | 64 | .action-sheet-group { 65 | margin-bottom: 5px; 66 | border-radius: $sheet-border-radius; 67 | background-color: #fff; 68 | .button { 69 | border-width: 1px 0px 0px 0px; 70 | border-radius: 0; 71 | 72 | &.active { 73 | background-color: transparent; 74 | color: inherit; 75 | } 76 | } 77 | .button:first-child:last-child { 78 | border-width: 0; 79 | } 80 | } 81 | 82 | .action-sheet-open { 83 | pointer-events: none; 84 | 85 | &.modal-open .modal { 86 | pointer-events: none; 87 | } 88 | 89 | .action-sheet-backdrop { 90 | pointer-events: auto; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /hooks/after_plugin_add/010_register_plugin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Push plugins to cordovaPlugins array after_plugin_add 5 | */ 6 | var fs = require('fs'), 7 | packageJSON = require('../../package.json'), 8 | path = require('path'); 9 | 10 | packageJSON.cordovaPlugins = packageJSON.cordovaPlugins || []; 11 | process.env.CORDOVA_PLUGINS.split(',').forEach(function (plugin) { 12 | var configString, 13 | idRegEx, 14 | id, 15 | pluginXmlPath, 16 | pluginToAdd; 17 | 18 | if(plugin.indexOf('https') != -1 || plugin.indexOf('git') != -1) { 19 | console.log('Installing plugin from url'); 20 | } 21 | 22 | if(plugin.indexOf('/') != -1) { 23 | try { 24 | pluginXmlPath = path.resolve(plugin, 'plugin.xml'); 25 | console.log('got pluginXmlPath:', pluginXmlPath); 26 | if (!fs.existsSync(pluginXmlPath)) { 27 | var errorMessage = ['There was no plugin.xml file found for path: ', pluginXmlPath].join(''); 28 | return; 29 | } 30 | 31 | configString = fs.readFileSync(pluginXmlPath,{encoding: 'utf8'}); 32 | idRegEx = new RegExp(']*id="(.*)"', 'i'); 33 | id = idRegEx.exec(configString)[1] 34 | pluginToAdd = {id: id, locator: plugin}; 35 | } catch(ex) { 36 | console.log('There was an error retrieving the plugin.xml filr from the 010_register_plugin.js hook', ex); 37 | } 38 | } else { 39 | pluginToAdd = plugin; 40 | } 41 | 42 | if(typeof pluginToAdd == 'string' && packageJSON.cordovaPlugins.indexOf(pluginToAdd) == -1) { 43 | packageJSON.cordovaPlugins.push(pluginToAdd); 44 | } else if (typeof pluginToAdd == 'object') { 45 | var pluginExists = false; 46 | packageJSON.cordovaPlugins.forEach(function(checkPlugin) { 47 | if(typeof checkPlugin == 'object' && checkPlugin.id == pluginToAdd.id) { 48 | pluginExists = true; 49 | } 50 | }) 51 | if(!pluginExists) { 52 | packageJSON.cordovaPlugins.push(pluginToAdd); 53 | } 54 | } 55 | }); 56 | 57 | fs.writeFileSync('package.json', JSON.stringify(packageJSON, null, 2)); 58 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/ionicons/_ionicons-animation.scss: -------------------------------------------------------------------------------- 1 | // Animation Icons 2 | // -------------------------- 3 | 4 | .#{$ionicons-prefix}spin { 5 | -webkit-animation: spin 1s infinite linear; 6 | -moz-animation: spin 1s infinite linear; 7 | -o-animation: spin 1s infinite linear; 8 | animation: spin 1s infinite linear; 9 | } 10 | 11 | @-moz-keyframes spin { 12 | 0% { -moz-transform: rotate(0deg); } 13 | 100% { -moz-transform: rotate(359deg); } 14 | } 15 | @-webkit-keyframes spin { 16 | 0% { -webkit-transform: rotate(0deg); } 17 | 100% { -webkit-transform: rotate(359deg); } 18 | } 19 | @-o-keyframes spin { 20 | 0% { -o-transform: rotate(0deg); } 21 | 100% { -o-transform: rotate(359deg); } 22 | } 23 | @-ms-keyframes spin { 24 | 0% { -ms-transform: rotate(0deg); } 25 | 100% { -ms-transform: rotate(359deg); } 26 | } 27 | @keyframes spin { 28 | 0% { transform: rotate(0deg); } 29 | 100% { transform: rotate(359deg); } 30 | } 31 | 32 | 33 | .#{$ionicons-prefix}loading-a, 34 | .#{$ionicons-prefix}loading-b, 35 | .#{$ionicons-prefix}loading-c, 36 | .#{$ionicons-prefix}loading-d, 37 | .#{$ionicons-prefix}looping, 38 | .#{$ionicons-prefix}refreshing, 39 | .#{$ionicons-prefix}ios7-reloading { 40 | @extend .ion; 41 | // must spin entire element for android 4.3 and below 42 | @extend .#{$ionicons-prefix}spin; 43 | } 44 | 45 | .#{$ionicons-prefix}loading-a { 46 | -webkit-animation-timing-function: steps(8, start); 47 | -moz-animation-timing-function: steps(8, start); 48 | animation-timing-function: steps(8, start); 49 | } 50 | 51 | .#{$ionicons-prefix}loading-a:before { 52 | @extend .#{$ionicons-prefix}load-a:before; 53 | } 54 | 55 | .#{$ionicons-prefix}loading-b:before { 56 | @extend .#{$ionicons-prefix}load-b:before; 57 | } 58 | 59 | .#{$ionicons-prefix}loading-c:before { 60 | @extend .#{$ionicons-prefix}load-c:before; 61 | } 62 | 63 | .#{$ionicons-prefix}loading-d:before { 64 | @extend .#{$ionicons-prefix}load-d:before; 65 | } 66 | 67 | .#{$ionicons-prefix}looping:before { 68 | @extend .#{$ionicons-prefix}loop:before; 69 | } 70 | 71 | .#{$ionicons-prefix}refreshing:before { 72 | @extend .#{$ionicons-prefix}refresh:before; 73 | } 74 | 75 | .#{$ionicons-prefix}ios7-reloading:before { 76 | @extend .#{$ionicons-prefix}ios7-reload:before; 77 | } 78 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_modal.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Modals 4 | * -------------------------------------------------- 5 | * Modals are independent windows that slide in from off-screen. 6 | */ 7 | 8 | .modal-backdrop { 9 | @include transition(background-color 300ms ease-in-out); 10 | position: fixed; 11 | top: 0; 12 | left: 0; 13 | z-index: $z-index-modal; 14 | width: 100%; 15 | height: 100%; 16 | background-color: $modal-backdrop-bg-inactive; 17 | 18 | &.active { 19 | background-color: $modal-backdrop-bg-active; 20 | } 21 | } 22 | 23 | .modal { 24 | display: block; 25 | position: absolute; 26 | top: 0; 27 | z-index: $z-index-modal; 28 | overflow: hidden; 29 | min-height: 100%; 30 | width: 100%; 31 | background-color: $modal-bg-color; 32 | } 33 | 34 | @media (min-width: $modal-inset-mode-break-point) { 35 | // inset mode is when the modal doesn't fill the entire 36 | // display but instead is centered within a large display 37 | .modal { 38 | top: $modal-inset-mode-top; 39 | right: $modal-inset-mode-right; 40 | bottom: $modal-inset-mode-bottom; 41 | left: $modal-inset-mode-left; 42 | overflow: visible; 43 | min-height: $modal-inset-mode-min-height; 44 | width: (100% - $modal-inset-mode-left - $modal-inset-mode-right); 45 | } 46 | 47 | .modal.ng-leave-active { 48 | bottom: 0; 49 | } 50 | 51 | // remove ios header padding from inset header 52 | .platform-ios.platform-cordova .modal-wrapper .modal{ 53 | .bar-header:not(.bar-subheader) { 54 | height: $bar-height; 55 | > * { 56 | margin-top: 0; 57 | } 58 | } 59 | .tabs-top > .tabs, 60 | .tabs.tabs-top { 61 | top: $bar-height; 62 | } 63 | .has-header, 64 | .bar-subheader { 65 | top: $bar-height; 66 | } 67 | .has-subheader { 68 | top: $bar-height + $bar-subheader-height; 69 | } 70 | .has-tabs-top { 71 | top: $bar-height + $tabs-height; 72 | } 73 | .has-header.has-subheader.has-tabs-top { 74 | top: $bar-height + $bar-subheader-height + $tabs-height; 75 | } 76 | } 77 | } 78 | 79 | // disable clicks on all but the modal 80 | .modal-open { 81 | pointer-events: none; 82 | 83 | .modal, 84 | .modal-backdrop { 85 | pointer-events: auto; 86 | } 87 | // prevent clicks on modal when loading overlay is active though 88 | &.loading-active { 89 | .modal, 90 | .modal-backdrop { 91 | pointer-events: none; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_popup.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Popups 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .popup-container { 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | bottom: 0; 12 | right: 0; 13 | background: rgba(0,0,0,0); 14 | 15 | @include display-flex(); 16 | @include justify-content(center); 17 | @include align-items(center); 18 | 19 | z-index: $z-index-popup; 20 | 21 | // Start hidden 22 | visibility: hidden; 23 | &.popup-showing { 24 | visibility: visible; 25 | } 26 | 27 | &.popup-hidden .popup { 28 | @include animation-name(scaleOut); 29 | @include animation-duration($popup-leave-animation-duration); 30 | @include animation-timing-function(ease-in-out); 31 | @include animation-fill-mode(both); 32 | } 33 | 34 | &.active .popup { 35 | @include animation-name(superScaleIn); 36 | @include animation-duration($popup-enter-animation-duration); 37 | @include animation-timing-function(ease-in-out); 38 | @include animation-fill-mode(both); 39 | } 40 | 41 | .popup { 42 | width: $popup-width; 43 | max-width: 100%; 44 | max-height: 90%; 45 | 46 | border-radius: $popup-border-radius; 47 | background-color: $popup-background-color; 48 | 49 | @include display-flex(); 50 | @include flex-direction(column); 51 | } 52 | } 53 | 54 | .popup-head { 55 | padding: 15px 10px; 56 | border-bottom: 1px solid #eee; 57 | text-align: center; 58 | } 59 | .popup-title { 60 | margin: 0; 61 | padding: 0; 62 | font-size: 15px; 63 | } 64 | .popup-sub-title { 65 | margin: 5px 0 0 0; 66 | padding: 0; 67 | font-weight: normal; 68 | font-size: 11px; 69 | } 70 | .popup-body { 71 | padding: 10px; 72 | overflow: scroll; 73 | } 74 | 75 | .popup-buttons { 76 | @include display-flex(); 77 | @include flex-direction(row); 78 | padding: 10px; 79 | min-height: $popup-button-min-height + 20; 80 | 81 | .button { 82 | @include flex(1); 83 | display: block; 84 | min-height: $popup-button-min-height; 85 | border-radius: $popup-button-border-radius; 86 | line-height: $popup-button-line-height; 87 | 88 | margin-right: 5px; 89 | &:last-child { 90 | margin-right: 0px; 91 | } 92 | } 93 | } 94 | 95 | .popup-open { 96 | pointer-events: none; 97 | 98 | &.modal-open .modal { 99 | pointer-events: none; 100 | } 101 | 102 | .popup-backdrop, .popup { 103 | pointer-events: auto; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cipher Safe for Android and iOS 2 | 3 | This project was created to compliment an article I wrote on [AirPair.com](http://www.airpair.com). It demonstrates how to use the Forge JavaScript cipher-text library and Firebase to create a 1Password, LastPass, or KeePass competitor with Ionic Framework. 4 | 5 | 6 | ## Requirements 7 | 8 | * Apache Cordova 4.0+ 9 | * Firebase 2.2.2+ 10 | * AngularFire 1.0.0+ 11 | * Ionic CLI 1.3.11+ 12 | 13 | 14 | ## Configuration 15 | 16 | Download this example project from GitHub and run the following commands: 17 | 18 | $ ionic platform add android 19 | 20 | The above command will add the Android build platform. 21 | 22 | This application requires you to have your own Firebase instance registered with **Email & Password** authentication enabled. 23 | Firebase permissions must be set as follows in the **Security & Roles** section: 24 | 25 | { 26 | "rules": { 27 | "users": { 28 | ".write": true, 29 | "$uid": { 30 | ".read": "auth != null && auth.uid == $uid" 31 | } 32 | } 33 | } 34 | } 35 | 36 | With your Firebase instance id in hand, open **www/js/app.js** and find the following line: 37 | 38 | fb = new Firebase("https://INSTANCE_ID_HERE.firebaseio.com/"); 39 | 40 | You will want to replace **INSTANCE_ID_HERE** with your actual instance id. 41 | 42 | 43 | ## Usage 44 | 45 | With this example project configured on your computer, run the following from the Terminal or command prompt: 46 | 47 | $ ionic build android 48 | 49 | Install the application binary to your device or simulator. 50 | 51 | The application is currently composed of six parts: 52 | 53 | 1. Firebase sign in 54 | 2. Master password creation 55 | 3. Master password unlocking 56 | 4. Password categories 57 | 5. Password lists 58 | 6. Password creation and viewing 59 | 60 | You will be required to sign in to Firebase to use this application. There is no offline compatibility in the current release. 61 | 62 | Passwords are encrypted before storing on Firebase and transferred over a secure HTTPS connection. 63 | 64 | 65 | ## Have a question or found a bug (compliments work too)? 66 | 67 | Tweet me on Twitter - [@nraboy](https://www.twitter.com/nraboy) 68 | 69 | 70 | ## Resources 71 | 72 | Nic Raboy's Code Blog - [https://blog.nraboy.com](https://blog.nraboy.com) 73 | 74 | Ionic Framework - [http://www.ionicframework.com](http://www.ionicframework.com) 75 | 76 | AngularJS - [http://www.angularjs.org](http://www.angularjs.org) 77 | 78 | Apache Cordova - [http://cordova.apache.org](http://cordova.apache.org) 79 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_list.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Lists 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .list { 8 | position: relative; 9 | padding-top: $item-border-width; 10 | padding-bottom: $item-border-width; 11 | padding-left: 0; // reset padding because ul and ol 12 | margin-bottom: 20px; 13 | } 14 | .list:last-child { 15 | margin-bottom: 0px; 16 | &.card{ 17 | margin-bottom:40px; 18 | } 19 | } 20 | 21 | 22 | /** 23 | * List Header 24 | * -------------------------------------------------- 25 | */ 26 | 27 | .list-header { 28 | margin-top: $list-header-margin-top; 29 | padding: $list-header-padding; 30 | background-color: $list-header-bg; 31 | color: $list-header-color; 32 | font-weight: bold; 33 | } 34 | 35 | // when its a card make sure it doesn't duplicate top and bottom borders 36 | .card.list .list-item { 37 | padding-right: 1px; 38 | padding-left: 1px; 39 | } 40 | 41 | 42 | /** 43 | * Cards and Inset Lists 44 | * -------------------------------------------------- 45 | * A card and list-inset are close to the same thing, except a card as a box shadow. 46 | */ 47 | 48 | .card, 49 | .list-inset { 50 | overflow: hidden; 51 | margin: ($content-padding * 2) $content-padding; 52 | border-radius: $card-border-radius; 53 | background-color: $card-body-bg; 54 | } 55 | 56 | .card { 57 | padding-top: $item-border-width; 58 | padding-bottom: $item-border-width; 59 | box-shadow: $card-box-shadow; 60 | 61 | .item { 62 | border-left: 0; 63 | border-right: 0; 64 | } 65 | .item:first-child { 66 | border-top: 0; 67 | } 68 | .item:last-child { 69 | border-bottom: 0; 70 | } 71 | } 72 | 73 | .padding { 74 | .card, .list-inset { 75 | margin-left: 0; 76 | margin-right: 0; 77 | } 78 | } 79 | 80 | .card .item, 81 | .list-inset .item, 82 | .padding > .list .item 83 | { 84 | &:first-child { 85 | border-top-left-radius: $card-border-radius; 86 | border-top-right-radius: $card-border-radius; 87 | 88 | .item-content { 89 | border-top-left-radius: $card-border-radius; 90 | border-top-right-radius: $card-border-radius; 91 | } 92 | } 93 | &:last-child { 94 | border-bottom-right-radius: $card-border-radius; 95 | border-bottom-left-radius: $card-border-radius; 96 | 97 | .item-content { 98 | border-bottom-right-radius: $card-border-radius; 99 | border-bottom-left-radius: $card-border-radius; 100 | } 101 | } 102 | } 103 | 104 | .card .item:last-child, 105 | .list-inset .item:last-child { 106 | margin-bottom: $item-border-width * -1; 107 | } 108 | 109 | .card .item, 110 | .list-inset .item, 111 | .padding > .list .item, 112 | .padding-horizontal > .list .item { 113 | margin-right: 0; 114 | margin-left: 0; 115 | 116 | &.item-input input { 117 | padding-right: 44px; 118 | } 119 | } 120 | .padding-left > .list .item { 121 | margin-left: 0; 122 | } 123 | .padding-right > .list .item { 124 | margin-right: 0; 125 | } 126 | -------------------------------------------------------------------------------- /hooks/after_prepare/010_add_platform_class.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Add Platform Class 4 | // v1.0 5 | // Automatically adds the platform class to the body tag 6 | // after the `prepare` command. By placing the platform CSS classes 7 | // directly in the HTML built for the platform, it speeds up 8 | // rendering the correct layout/style for the specific platform 9 | // instead of waiting for the JS to figure out the correct classes. 10 | 11 | var fs = require('fs'); 12 | var path = require('path'); 13 | 14 | var rootdir = process.argv[2]; 15 | 16 | function addPlatformBodyTag(indexPath, platform) { 17 | // add the platform class to the body tag 18 | try { 19 | var platformClass = 'platform-' + platform; 20 | var cordovaClass = 'platform-cordova platform-webview'; 21 | 22 | var html = fs.readFileSync(indexPath, 'utf8'); 23 | 24 | var bodyTag = findBodyTag(html); 25 | if(!bodyTag) return; // no opening body tag, something's wrong 26 | 27 | if(bodyTag.indexOf(platformClass) > -1) return; // already added 28 | 29 | var newBodyTag = bodyTag; 30 | 31 | var classAttr = findClassAttr(bodyTag); 32 | if(classAttr) { 33 | // body tag has existing class attribute, add the classname 34 | var endingQuote = classAttr.substring(classAttr.length-1); 35 | var newClassAttr = classAttr.substring(0, classAttr.length-1); 36 | newClassAttr += ' ' + platformClass + ' ' + cordovaClass + endingQuote; 37 | newBodyTag = bodyTag.replace(classAttr, newClassAttr); 38 | 39 | } else { 40 | // add class attribute to the body tag 41 | newBodyTag = bodyTag.replace('>', ' class="' + platformClass + ' ' + cordovaClass + '">'); 42 | } 43 | 44 | html = html.replace(bodyTag, newBodyTag); 45 | 46 | fs.writeFileSync(indexPath, html, 'utf8'); 47 | 48 | process.stdout.write('add to body class: ' + platformClass + '\n'); 49 | } catch(e) { 50 | process.stdout.write(e); 51 | } 52 | } 53 | 54 | function findBodyTag(html) { 55 | // get the body tag 56 | try{ 57 | return html.match(/])(.*?)>/gi)[0]; 58 | }catch(e){} 59 | } 60 | 61 | function findClassAttr(bodyTag) { 62 | // get the body tag's class attribute 63 | try{ 64 | return bodyTag.match(/ class=["|'](.*?)["|']/gi)[0]; 65 | }catch(e){} 66 | } 67 | 68 | if (rootdir) { 69 | 70 | // go through each of the platform directories that have been prepared 71 | var platforms = (process.env.CORDOVA_PLATFORMS ? process.env.CORDOVA_PLATFORMS.split(',') : []); 72 | 73 | for(var x=0; x 21 | # Cordova Hooks 22 | 23 | This directory may contain scripts used to customize cordova commands. This 24 | directory used to exist at `.cordova/hooks`, but has now been moved to the 25 | project root. Any scripts you add to these directories will be executed before 26 | and after the commands corresponding to the directory name. Useful for 27 | integrating your own build systems or integrating with version control systems. 28 | 29 | __Remember__: Make your scripts executable. 30 | 31 | ## Hook Directories 32 | The following subdirectories will be used for hooks: 33 | 34 | after_build/ 35 | after_compile/ 36 | after_docs/ 37 | after_emulate/ 38 | after_platform_add/ 39 | after_platform_rm/ 40 | after_platform_ls/ 41 | after_plugin_add/ 42 | after_plugin_ls/ 43 | after_plugin_rm/ 44 | after_plugin_search/ 45 | after_prepare/ 46 | after_run/ 47 | after_serve/ 48 | before_build/ 49 | before_compile/ 50 | before_docs/ 51 | before_emulate/ 52 | before_platform_add/ 53 | before_platform_rm/ 54 | before_platform_ls/ 55 | before_plugin_add/ 56 | before_plugin_ls/ 57 | before_plugin_rm/ 58 | before_plugin_search/ 59 | before_prepare/ 60 | before_run/ 61 | before_serve/ 62 | pre_package/ <-- Windows 8 and Windows Phone only. 63 | 64 | ## Script Interface 65 | 66 | All scripts are run from the project's root directory and have the root directory passes as the first argument. All other options are passed to the script using environment variables: 67 | 68 | * CORDOVA_VERSION - The version of the Cordova-CLI. 69 | * CORDOVA_PLATFORMS - Comma separated list of platforms that the command applies to (e.g.: android, ios). 70 | * CORDOVA_PLUGINS - Comma separated list of plugin IDs that the command applies to (e.g.: org.apache.cordova.file, org.apache.cordova.file-transfer) 71 | * CORDOVA_HOOK - Path to the hook that is being executed. 72 | * CORDOVA_CMDLINE - The exact command-line arguments passed to cordova (e.g.: cordova run ios --emulate) 73 | 74 | If a script returns a non-zero exit code, then the parent cordova command will be aborted. 75 | 76 | 77 | ## Writing hooks 78 | 79 | We highly recommend writting your hooks using Node.js so that they are 80 | cross-platform. Some good examples are shown here: 81 | 82 | [http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/](http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/) 83 | 84 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_select.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Select 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .item-select { 8 | position: relative; 9 | 10 | select { 11 | @include appearance(none); 12 | position: absolute; 13 | top: 0; 14 | right: 0; 15 | padding: ($item-padding - 2) ($item-padding * 3) ($item-padding) $item-padding; 16 | max-width: 65%; 17 | 18 | border: none; 19 | background: $item-default-bg; 20 | color: #333; 21 | 22 | // hack to hide default dropdown arrow in FF 23 | text-indent: .01px; 24 | text-overflow: ''; 25 | 26 | white-space: nowrap; 27 | font-size: $font-size-base; 28 | 29 | cursor: pointer; 30 | direction: rtl; // right align the select text 31 | } 32 | 33 | select::-ms-expand { 34 | // hide default dropdown arrow in IE 35 | display: none; 36 | } 37 | 38 | option { 39 | direction: ltr; 40 | } 41 | 42 | &:after { 43 | position: absolute; 44 | top: 50%; 45 | right: $item-padding; 46 | margin-top: -3px; 47 | width: 0; 48 | height: 0; 49 | border-top: 5px solid; 50 | border-right: 5px solid rgba(0, 0, 0, 0); 51 | border-left: 5px solid rgba(0, 0, 0, 0); 52 | color: #999; 53 | content: ""; 54 | pointer-events: none; 55 | } 56 | &.item-light { 57 | select{ 58 | background:$item-light-bg; 59 | color:$item-light-text; 60 | } 61 | } 62 | &.item-stable { 63 | select{ 64 | background:$item-stable-bg; 65 | color:$item-stable-text; 66 | } 67 | &:after, .input-label{ 68 | color:darken($item-stable-border,30%); 69 | } 70 | } 71 | &.item-positive { 72 | select{ 73 | background:$item-positive-bg; 74 | color:$item-positive-text; 75 | } 76 | &:after, .input-label{ 77 | color:$item-positive-text; 78 | } 79 | } 80 | &.item-calm { 81 | select{ 82 | background:$item-calm-bg; 83 | color:$item-calm-text; 84 | } 85 | &:after, .input-label{ 86 | color:$item-calm-text; 87 | } 88 | } 89 | &.item-assertive { 90 | select{ 91 | background:$item-assertive-bg; 92 | color:$item-assertive-text; 93 | } 94 | &:after, .input-label{ 95 | color:$item-assertive-text; 96 | } 97 | } 98 | &.item-balanced { 99 | select{ 100 | background:$item-balanced-bg; 101 | color:$item-balanced-text; 102 | } 103 | &:after, .input-label{ 104 | color:$item-balanced-text; 105 | } 106 | } 107 | &.item-energized { 108 | select{ 109 | background:$item-energized-bg; 110 | color:$item-energized-text; 111 | } 112 | &:after, .input-label{ 113 | color:$item-energized-text; 114 | } 115 | } 116 | &.item-royal { 117 | select{ 118 | background:$item-royal-bg; 119 | color:$item-royal-text; 120 | } 121 | &:after, .input-label{ 122 | color:$item-royal-text; 123 | } 124 | } 125 | &.item-dark { 126 | select{ 127 | background:$item-dark-bg; 128 | color:$item-dark-text; 129 | } 130 | &:after, .input-label{ 131 | color:$item-dark-text; 132 | } 133 | } 134 | } 135 | 136 | select { 137 | &[multiple], 138 | &[size] { 139 | height: auto; 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_range.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Range 4 | * -------------------------------------------------- 5 | */ 6 | 7 | input[type="range"] { 8 | display: inline-block; 9 | overflow: hidden; 10 | margin-top: 5px; 11 | margin-bottom: 5px; 12 | padding-right: 2px; 13 | padding-left: 1px; 14 | width: auto; 15 | height: $range-slider-height + 15; 16 | outline: none; 17 | background: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, $range-default-track-bg), color-stop(100%, $range-default-track-bg)); 18 | background: linear-gradient(to right, $range-default-track-bg 0%, $range-default-track-bg 100%); 19 | background-position: center; 20 | background-size: 99% $range-track-height; 21 | background-repeat: no-repeat; 22 | -webkit-appearance: none; 23 | 24 | &::-webkit-slider-thumb { 25 | position: relative; 26 | width: $range-slider-width; 27 | height: $range-slider-height; 28 | border-radius: $range-slider-border-radius; 29 | background-color: $toggle-handle-off-bg-color; 30 | box-shadow: $range-slider-box-shadow; 31 | cursor: pointer; 32 | -webkit-appearance: none; 33 | border: 0; 34 | } 35 | 36 | &::-webkit-slider-thumb:before { 37 | /* what creates the colorful line on the left side of the slider */ 38 | position: absolute; 39 | top: ($range-slider-height / 2) - ($range-track-height / 2); 40 | left: -2001px; 41 | width: 2000px; 42 | height: $range-track-height; 43 | background: $dark; 44 | content: ' '; 45 | } 46 | 47 | &::-webkit-slider-thumb:after { 48 | /* create a larger (but hidden) hit area */ 49 | position: absolute; 50 | top: -15px; 51 | left: -15px; 52 | padding: 30px; 53 | content: ' '; 54 | //background: red; 55 | //opacity: .5; 56 | } 57 | 58 | } 59 | 60 | .range { 61 | @include display-flex(); 62 | @include align-items(center); 63 | padding: 2px 11px; 64 | 65 | &.range-light { 66 | input { @include range-style($range-light-track-bg); } 67 | } 68 | &.range-stable { 69 | input { @include range-style($range-stable-track-bg); } 70 | } 71 | &.range-positive { 72 | input { @include range-style($range-positive-track-bg); } 73 | } 74 | &.range-calm { 75 | input { @include range-style($range-calm-track-bg); } 76 | } 77 | &.range-balanced { 78 | input { @include range-style($range-balanced-track-bg); } 79 | } 80 | &.range-assertive { 81 | input { @include range-style($range-assertive-track-bg); } 82 | } 83 | &.range-energized { 84 | input { @include range-style($range-energized-track-bg); } 85 | } 86 | &.range-royal { 87 | input { @include range-style($range-royal-track-bg); } 88 | } 89 | &.range-dark { 90 | input { @include range-style($range-dark-track-bg); } 91 | } 92 | } 93 | 94 | .range .icon { 95 | @include flex(0); 96 | display: block; 97 | min-width: $range-icon-size; 98 | text-align: center; 99 | font-size: $range-icon-size; 100 | } 101 | 102 | .range input { 103 | @include flex(1); 104 | display: block; 105 | margin-right: 10px; 106 | margin-left: 10px; 107 | } 108 | 109 | .range-label { 110 | @include flex(0, 0, auto); 111 | display: block; 112 | white-space: nowrap; 113 | } 114 | 115 | .range-label:first-child { 116 | padding-left: 5px; 117 | } 118 | .range input + .range-label { 119 | padding-right: 5px; 120 | padding-left: 0; 121 | } 122 | 123 | -------------------------------------------------------------------------------- /www/lib/ionic/js/angular/angular-resource.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.6 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(I,d,B){'use strict';function D(f,q){q=q||{};d.forEach(q,function(d,h){delete q[h]});for(var h in f)!f.hasOwnProperty(h)||"$"===h.charAt(0)&&"$"===h.charAt(1)||(q[h]=f[h]);return q}var w=d.$$minErr("$resource"),C=/^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;d.module("ngResource",["ng"]).provider("$resource",function(){var f=this;this.defaults={stripTrailingSlashes:!0,actions:{get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}}}; 7 | this.$get=["$http","$q",function(q,h){function t(d,g){this.template=d;this.defaults=s({},f.defaults,g);this.urlParams={}}function v(x,g,l,m){function c(b,k){var c={};k=s({},g,k);r(k,function(a,k){u(a)&&(a=a());var d;if(a&&a.charAt&&"@"==a.charAt(0)){d=b;var e=a.substr(1);if(null==e||""===e||"hasOwnProperty"===e||!C.test("."+e))throw w("badmember",e);for(var e=e.split("."),n=0,g=e.length;n=c;e--)d.end&&d.end(f[e]);f.length=c}}"string"!==typeof a&&(a=null===a||"undefined"===typeof a?"":""+a);var b,k,f=[],m=a,l;for(f.last=function(){return f[f.length-1]};a;){l="";k=!0;if(f.last()&&x[f.last()])a=a.replace(new RegExp("(.*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(a,b){b=b.replace(H,"$1").replace(I,"$1");d.chars&&d.chars(r(b));return""}),e("",f.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",b)===b&&(d.comment&&d.comment(a.substring(4, 8 | b)),a=a.substring(b+3),k=!1);else if(y.test(a)){if(b=a.match(y))a=a.replace(b[0],""),k=!1}else if(J.test(a)){if(b=a.match(z))a=a.substring(b[0].length),b[0].replace(z,e),k=!1}else K.test(a)&&((b=a.match(A))?(b[4]&&(a=a.substring(b[0].length),b[0].replace(A,c)),k=!1):(l+="<",a=a.substring(1)));k&&(b=a.indexOf("<"),l+=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),d.chars&&d.chars(r(l)))}if(a==m)throw L("badparse",a);m=a}e()}function r(a){if(!a)return"";var d=M.exec(a);a=d[1];var c=d[3];if(d=d[2])q.innerHTML= 9 | d.replace(//g,">")}function s(a,d){var c=!1,e=h.bind(a,a.push);return{start:function(a,k,f){a=h.lowercase(a);!c&&x[a]&&(c=a);c||!0!==C[a]||(e("<"),e(a),h.forEach(k,function(c,f){var k= 10 | h.lowercase(f),g="img"===a&&"src"===k||"background"===k;!0!==P[k]||!0===D[k]&&!d(c,g)||(e(" "),e(f),e('="'),e(B(c)),e('"'))}),e(f?"/>":">"))},end:function(a){a=h.lowercase(a);c||!0!==C[a]||(e(""));a==c&&(c=!1)},chars:function(a){c||e(B(a))}}}var L=h.$$minErr("$sanitize"),A=/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,z=/^<\/\s*([\w:-]+)[^>]*>/,G=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,K=/^]*?)>/i,I=/"\u201d\u2019]/,c=/^mailto:/;return function(e,b){function k(a){a&&g.push(E(a))} 15 | function f(a,c){g.push("');k(c);g.push("")}if(!e)return e;for(var m,l=e,g=[],n,p;m=l.match(d);)n=m[0],m[2]||m[4]||(n=(m[3]?"http://":"mailto:")+n),p=m.index,k(l.substr(0,p)),f(n,m[0].replace(c,"")),l=l.substring(p+m[0].length);k(l);return a(g.join(""))}}])})(window,window.angular); 16 | //# sourceMappingURL=angular-sanitize.min.js.map 17 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_form.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Forms 3 | * -------------------------------------------------- 4 | */ 5 | 6 | // Make all forms have space below them 7 | form { 8 | margin: 0 0 $line-height-base; 9 | } 10 | 11 | // Groups of fields with labels on top (legends) 12 | legend { 13 | display: block; 14 | margin-bottom: $line-height-base; 15 | padding: 0; 16 | width: 100%; 17 | border: $input-border-width solid $input-border; 18 | color: $dark; 19 | font-size: $font-size-base * 1.5; 20 | line-height: $line-height-base * 2; 21 | 22 | small { 23 | color: $stable; 24 | font-size: $line-height-base * .75; 25 | } 26 | } 27 | 28 | // Set font for forms 29 | label, 30 | input, 31 | button, 32 | select, 33 | textarea { 34 | @include font-shorthand($font-size-base, normal, $line-height-base); // Set size, weight, line-height here 35 | } 36 | input, 37 | button, 38 | select, 39 | textarea { 40 | font-family: $font-family-base; // And only set font-family here for those that need it (note the missing label element) 41 | } 42 | 43 | 44 | // Input List 45 | // ------------------------------- 46 | 47 | .item-input { 48 | @include display-flex(); 49 | @include align-items(center); 50 | position: relative; 51 | overflow: hidden; 52 | padding: 6px 0 5px 16px; 53 | 54 | input { 55 | @include border-radius(0); 56 | @include flex(1, 0, 220px); 57 | @include appearance(none); 58 | margin: 0; 59 | padding-right: 24px; 60 | background-color: transparent; 61 | } 62 | 63 | .button .icon { 64 | @include flex(0, 0, 24px); 65 | position: static; 66 | display: inline-block; 67 | height: auto; 68 | text-align: center; 69 | font-size: 16px; 70 | } 71 | 72 | .button-bar { 73 | @include border-radius(0); 74 | @include flex(1, 0, 220px); 75 | @include appearance(none); 76 | } 77 | 78 | .icon { 79 | min-width: 14px; 80 | } 81 | } 82 | 83 | .item-input-inset { 84 | @include display-flex(); 85 | @include align-items(center); 86 | position: relative; 87 | overflow: hidden; 88 | padding: ($item-padding / 3) * 2; 89 | } 90 | 91 | .item-input-wrapper { 92 | @include display-flex(); 93 | @include flex(1, 0); 94 | @include align-items(center); 95 | @include border-radius(4px); 96 | padding-right: 8px; 97 | padding-left: 8px; 98 | background: #eee; 99 | } 100 | 101 | .item-input-inset .item-input-wrapper input { 102 | padding-left: 4px; 103 | height: 29px; 104 | background: transparent; 105 | line-height: 18px; 106 | } 107 | 108 | .item-input-wrapper ~ .button { 109 | margin-left: ($item-padding / 3) * 2; 110 | } 111 | 112 | .input-label { 113 | @include flex(1, 0, 100px); 114 | display: table; 115 | padding: 7px 10px 7px 0px; 116 | max-width: 200px; 117 | width: 35%; 118 | color: $input-label-color; 119 | font-size: 16px; 120 | } 121 | 122 | .placeholder-icon { 123 | color: #aaa; 124 | &:first-child { 125 | padding-right: 6px; 126 | } 127 | &:last-child { 128 | padding-left: 6px; 129 | } 130 | } 131 | 132 | .item-stacked-label { 133 | display: block; 134 | background-color: transparent; 135 | box-shadow: none; 136 | 137 | .input-label, .icon { 138 | display: inline-block; 139 | padding: 4px 0 0 0px; 140 | vertical-align: middle; 141 | } 142 | } 143 | 144 | .item-stacked-label input, 145 | .item-stacked-label textarea { 146 | @include border-radius(2px); 147 | padding: 4px 8px 3px 0; 148 | border: none; 149 | background-color: $input-bg; 150 | } 151 | .item-stacked-label input { 152 | overflow: hidden; 153 | height: $line-height-computed + $font-size-base + 12px; 154 | } 155 | 156 | .item-floating-label { 157 | display: block; 158 | background-color: transparent; 159 | box-shadow: none; 160 | 161 | .input-label { 162 | position: relative; 163 | padding: 5px 0 0 0; 164 | opacity: 0; 165 | top: 10px; 166 | @include transition(opacity .15s ease-in, top .2s linear); 167 | 168 | &.has-input { 169 | opacity: 1; 170 | top: 0; 171 | @include transition(opacity .15s ease-in, top .2s linear); 172 | } 173 | } 174 | } 175 | 176 | 177 | // Form Controls 178 | // ------------------------------- 179 | 180 | // Shared size and type resets 181 | textarea, 182 | input[type="text"], 183 | input[type="password"], 184 | input[type="datetime"], 185 | input[type="datetime-local"], 186 | input[type="date"], 187 | input[type="month"], 188 | input[type="time"], 189 | input[type="week"], 190 | input[type="number"], 191 | input[type="email"], 192 | input[type="url"], 193 | input[type="search"], 194 | input[type="tel"], 195 | input[type="color"] { 196 | display: block; 197 | padding-top: 2px; 198 | padding-left: 0; 199 | height: $line-height-computed + $font-size-base; 200 | color: $input-color; 201 | vertical-align: middle; 202 | font-size: $font-size-base; 203 | line-height: $font-size-base + 2; 204 | } 205 | 206 | .platform-ios, 207 | .platform-android { 208 | input[type="datetime-local"], 209 | input[type="date"], 210 | input[type="month"], 211 | input[type="time"], 212 | input[type="week"] { 213 | padding-top: 8px; 214 | } 215 | } 216 | 217 | input, 218 | textarea { 219 | width: 100%; 220 | } 221 | textarea { 222 | padding-left: 0; 223 | @include placeholder($input-color-placeholder, -3px); 224 | } 225 | 226 | // Reset height since textareas have rows 227 | textarea { 228 | height: auto; 229 | } 230 | 231 | // Everything else 232 | textarea, 233 | input[type="text"], 234 | input[type="password"], 235 | input[type="datetime"], 236 | input[type="datetime-local"], 237 | input[type="date"], 238 | input[type="month"], 239 | input[type="time"], 240 | input[type="week"], 241 | input[type="number"], 242 | input[type="email"], 243 | input[type="url"], 244 | input[type="search"], 245 | input[type="tel"], 246 | input[type="color"] { 247 | border: 0; 248 | } 249 | 250 | // Position radios and checkboxes better 251 | input[type="radio"], 252 | input[type="checkbox"] { 253 | margin: 0; 254 | line-height: normal; 255 | } 256 | 257 | // Reset width of input images, buttons, radios, checkboxes 258 | input[type="file"], 259 | input[type="image"], 260 | input[type="submit"], 261 | input[type="reset"], 262 | input[type="button"], 263 | input[type="radio"], 264 | input[type="checkbox"] { 265 | width: auto; // Override of generic input selector 266 | } 267 | 268 | // Set the height of file to match text inputs 269 | input[type="file"] { 270 | line-height: $input-height-base; 271 | } 272 | 273 | // Text input classes to hide text caret during scroll 274 | .previous-input-focus, 275 | .cloned-text-input + input, 276 | .cloned-text-input + textarea { 277 | position: absolute !important; 278 | left: -9999px; 279 | width: 200px; 280 | } 281 | 282 | 283 | // Placeholder 284 | // ------------------------------- 285 | input, 286 | textarea { 287 | @include placeholder(); 288 | } 289 | 290 | 291 | // DISABLED STATE 292 | // ------------------------------- 293 | 294 | // Disabled and read-only inputs 295 | input[disabled], 296 | select[disabled], 297 | textarea[disabled], 298 | input[readonly]:not(.cloned-text-input), 299 | textarea[readonly]:not(.cloned-text-input), 300 | select[readonly] { 301 | background-color: $input-bg-disabled; 302 | cursor: not-allowed; 303 | } 304 | // Explicitly reset the colors here 305 | input[type="radio"][disabled], 306 | input[type="checkbox"][disabled], 307 | input[type="radio"][readonly], 308 | input[type="checkbox"][readonly] { 309 | background-color: transparent; 310 | } 311 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_button.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Buttons 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .button { 8 | // set the color defaults 9 | @include button-style($button-default-bg, $button-default-border, $button-default-active-bg, $button-default-active-border, $button-default-text); 10 | 11 | position: relative; 12 | display: inline-block; 13 | margin: 0; 14 | padding: 0 $button-padding; 15 | 16 | min-width: ($button-padding * 3) + $button-font-size; 17 | min-height: $button-height + 5px; 18 | 19 | border-width: $button-border-width; 20 | border-style: solid; 21 | border-radius: $button-border-radius; 22 | 23 | vertical-align: top; 24 | text-align: center; 25 | 26 | text-overflow: ellipsis; 27 | font-size: $button-font-size; 28 | line-height: $button-height - $button-border-width + 1px; 29 | 30 | cursor: pointer; 31 | 32 | &:after { 33 | // used to create a larger button "hit" area 34 | position: absolute; 35 | top: -6px; 36 | right: -6px; 37 | bottom: -6px; 38 | left: -6px; 39 | content: ' '; 40 | } 41 | 42 | .icon { 43 | vertical-align: top; 44 | pointer-events: none; 45 | } 46 | 47 | .icon:before, 48 | &.icon:before, 49 | &.icon-left:before, 50 | &.icon-right:before { 51 | display: inline-block; 52 | padding: 0 0 $button-border-width 0; 53 | vertical-align: inherit; 54 | font-size: $button-icon-size; 55 | line-height: $button-height - $button-border-width; 56 | pointer-events: none; 57 | } 58 | &.icon-left:before { 59 | float: left; 60 | padding-right: .2em; 61 | padding-left: 0; 62 | } 63 | &.icon-right:before { 64 | float: right; 65 | padding-right: 0; 66 | padding-left: .2em; 67 | } 68 | 69 | &.button-block, &.button-full { 70 | margin-top: $button-block-margin; 71 | margin-bottom: $button-block-margin; 72 | } 73 | 74 | &.button-light { 75 | @include button-style($button-light-bg, $button-light-border, $button-light-active-bg, $button-light-active-border, $button-light-text); 76 | @include button-clear($button-light-border); 77 | @include button-outline($button-light-border); 78 | } 79 | 80 | &.button-stable { 81 | @include button-style($button-stable-bg, $button-stable-border, $button-stable-active-bg, $button-stable-active-border, $button-stable-text); 82 | @include button-clear($button-stable-border); 83 | @include button-outline($button-stable-border); 84 | } 85 | 86 | &.button-positive { 87 | @include button-style($button-positive-bg, $button-positive-border, $button-positive-active-bg, $button-positive-active-border, $button-positive-text); 88 | @include button-clear($button-positive-bg); 89 | @include button-outline($button-positive-bg); 90 | } 91 | 92 | &.button-calm { 93 | @include button-style($button-calm-bg, $button-calm-border, $button-calm-active-bg, $button-calm-active-border, $button-calm-text); 94 | @include button-clear($button-calm-bg); 95 | @include button-outline($button-calm-bg); 96 | } 97 | 98 | &.button-assertive { 99 | @include button-style($button-assertive-bg, $button-assertive-border, $button-assertive-active-bg, $button-assertive-active-border, $button-assertive-text); 100 | @include button-clear($button-assertive-bg); 101 | @include button-outline($button-assertive-bg); 102 | } 103 | 104 | &.button-balanced { 105 | @include button-style($button-balanced-bg, $button-balanced-border, $button-balanced-active-bg, $button-balanced-active-border, $button-balanced-text); 106 | @include button-clear($button-balanced-bg); 107 | @include button-outline($button-balanced-bg); 108 | } 109 | 110 | &.button-energized { 111 | @include button-style($button-energized-bg, $button-energized-border, $button-energized-active-bg, $button-energized-active-border, $button-energized-text); 112 | @include button-clear($button-energized-bg); 113 | @include button-outline($button-energized-bg); 114 | } 115 | 116 | &.button-royal { 117 | @include button-style($button-royal-bg, $button-royal-border, $button-royal-active-bg, $button-royal-active-border, $button-royal-text); 118 | @include button-clear($button-royal-bg); 119 | @include button-outline($button-royal-bg); 120 | } 121 | 122 | &.button-dark { 123 | @include button-style($button-dark-bg, $button-dark-border, $button-dark-active-bg, $button-dark-active-border, $button-dark-text); 124 | @include button-clear($button-dark-bg); 125 | @include button-outline($button-dark-bg); 126 | } 127 | } 128 | 129 | .button-small { 130 | padding: 2px $button-small-padding 1px; 131 | min-width: $button-small-height; 132 | min-height: $button-small-height + 2; 133 | font-size: $button-small-font-size; 134 | line-height: $button-small-height - $button-border-width - 1; 135 | 136 | .icon:before, 137 | &.icon:before, 138 | &.icon-left:before, 139 | &.icon-right:before { 140 | font-size: $button-small-icon-size; 141 | line-height: $button-small-icon-size + 3; 142 | margin-top: 3px; 143 | } 144 | } 145 | 146 | .button-large { 147 | padding: 0 $button-large-padding; 148 | min-width: ($button-large-padding * 3) + $button-large-font-size; 149 | min-height: $button-large-height + 5; 150 | font-size: $button-large-font-size; 151 | line-height: $button-large-height - $button-border-width; 152 | 153 | .icon:before, 154 | &.icon:before, 155 | &.icon-left:before, 156 | &.icon-right:before { 157 | padding-bottom: ($button-border-width * 2); 158 | font-size: $button-large-icon-size; 159 | line-height: $button-large-height - ($button-border-width * 2) - 1; 160 | } 161 | } 162 | 163 | .button-icon { 164 | @include transition(opacity .1s); 165 | padding: 0 6px; 166 | min-width: initial; 167 | border-color: transparent; 168 | background: none; 169 | 170 | &.button.active, 171 | &.button.activated { 172 | border-color: transparent; 173 | background: none; 174 | box-shadow: none; 175 | opacity: 0.3; 176 | } 177 | 178 | .icon:before, 179 | &.icon:before { 180 | font-size: $button-large-icon-size; 181 | } 182 | } 183 | 184 | .button-clear { 185 | @include button-clear($button-default-border); 186 | @include transition(opacity .1s); 187 | padding: 0 $button-clear-padding; 188 | max-height: $button-height; 189 | border-color: transparent; 190 | background: none; 191 | box-shadow: none; 192 | 193 | &.active, 194 | &.activated { 195 | opacity: 0.3; 196 | } 197 | } 198 | 199 | .button-outline { 200 | @include button-outline($button-default-border); 201 | @include transition(opacity .1s); 202 | background: none; 203 | box-shadow: none; 204 | } 205 | 206 | .padding > .button.button-block:first-child { 207 | margin-top: 0; 208 | } 209 | 210 | .button-block { 211 | display: block; 212 | clear: both; 213 | 214 | &:after { 215 | clear: both; 216 | } 217 | } 218 | 219 | .button-full, 220 | .button-full > .button { 221 | display: block; 222 | margin-right: 0; 223 | margin-left: 0; 224 | border-right-width: 0; 225 | border-left-width: 0; 226 | border-radius: 0; 227 | } 228 | 229 | button.button-block, 230 | button.button-full, 231 | .button-full > button.button, 232 | input.button.button-block { 233 | width: 100%; 234 | } 235 | 236 | a.button { 237 | text-decoration: none; 238 | 239 | .icon:before, 240 | &.icon:before, 241 | &.icon-left:before, 242 | &.icon-right:before { 243 | margin-top: 2px; 244 | } 245 | } 246 | 247 | .button.disabled, 248 | .button[disabled] { 249 | opacity: .4; 250 | cursor: default !important; 251 | pointer-events: none; 252 | } 253 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_reset.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Resets 4 | * -------------------------------------------------- 5 | * Adapted from normalize.css and some reset.css. We don't care even one 6 | * bit about old IE, so we don't need any hacks for that in here. 7 | * 8 | * There are probably other things we could remove here, as well. 9 | * 10 | * normalize.css v2.1.2 | MIT License | git.io/normalize 11 | 12 | * Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) 13 | * http://cssreset.com 14 | */ 15 | 16 | html, body, div, span, applet, object, iframe, 17 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 18 | a, abbr, acronym, address, big, cite, code, 19 | del, dfn, em, img, ins, kbd, q, s, samp, 20 | small, strike, strong, sub, sup, tt, var, 21 | b, i, u, center, 22 | dl, dt, dd, ol, ul, li, 23 | fieldset, form, label, legend, 24 | table, caption, tbody, tfoot, thead, tr, th, td, 25 | article, aside, canvas, details, embed, fieldset, 26 | figure, figcaption, footer, header, hgroup, 27 | menu, nav, output, ruby, section, summary, 28 | time, mark, audio, video { 29 | margin: 0; 30 | padding: 0; 31 | border: 0; 32 | vertical-align: baseline; 33 | font: inherit; 34 | font-size: 100%; 35 | } 36 | 37 | ol, ul { 38 | list-style: none; 39 | } 40 | blockquote, q { 41 | quotes: none; 42 | } 43 | blockquote:before, blockquote:after, 44 | q:before, q:after { 45 | content: ''; 46 | content: none; 47 | } 48 | 49 | /** 50 | * Prevent modern browsers from displaying `audio` without controls. 51 | * Remove excess height in iOS 5 devices. 52 | */ 53 | 54 | audio:not([controls]) { 55 | display: none; 56 | height: 0; 57 | } 58 | 59 | /** 60 | * Hide the `template` element in IE, Safari, and Firefox < 22. 61 | */ 62 | 63 | [hidden], 64 | template { 65 | display: none; 66 | } 67 | 68 | script { 69 | display: none !important; 70 | } 71 | 72 | /* ========================================================================== 73 | Base 74 | ========================================================================== */ 75 | 76 | /** 77 | * 1. Set default font family to sans-serif. 78 | * 2. Prevent iOS text size adjust after orientation change, without disabling 79 | * user zoom. 80 | */ 81 | 82 | html { 83 | @include user-select(none); 84 | font-family: sans-serif; /* 1 */ 85 | -webkit-text-size-adjust: 100%; 86 | -ms-text-size-adjust: 100%; /* 2 */ 87 | -webkit-text-size-adjust: 100%; /* 2 */ 88 | } 89 | 90 | /** 91 | * Remove default margin. 92 | */ 93 | 94 | body { 95 | margin: 0; 96 | line-height: 1; 97 | } 98 | 99 | 100 | /** 101 | * Remove default outlines. 102 | */ 103 | a, 104 | button, 105 | :focus, 106 | a:focus, 107 | button:focus, 108 | a:active, 109 | a:hover { 110 | outline: 0; 111 | } 112 | 113 | /* * 114 | * Remove tap highlight color 115 | */ 116 | 117 | a { 118 | -webkit-user-drag: none; 119 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 120 | -webkit-tap-highlight-color: transparent; 121 | 122 | &[href]:hover { 123 | cursor: pointer; 124 | } 125 | } 126 | 127 | /* ========================================================================== 128 | Typography 129 | ========================================================================== */ 130 | 131 | 132 | /** 133 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 134 | */ 135 | 136 | b, 137 | strong { 138 | font-weight: bold; 139 | } 140 | 141 | /** 142 | * Address styling not present in Safari 5 and Chrome. 143 | */ 144 | 145 | dfn { 146 | font-style: italic; 147 | } 148 | 149 | /** 150 | * Address differences between Firefox and other browsers. 151 | */ 152 | 153 | hr { 154 | -moz-box-sizing: content-box; 155 | box-sizing: content-box; 156 | height: 0; 157 | } 158 | 159 | 160 | /** 161 | * Correct font family set oddly in Safari 5 and Chrome. 162 | */ 163 | 164 | code, 165 | kbd, 166 | pre, 167 | samp { 168 | font-size: 1em; 169 | font-family: monospace, serif; 170 | } 171 | 172 | /** 173 | * Improve readability of pre-formatted text in all browsers. 174 | */ 175 | 176 | pre { 177 | white-space: pre-wrap; 178 | } 179 | 180 | /** 181 | * Set consistent quote types. 182 | */ 183 | 184 | q { 185 | quotes: "\201C" "\201D" "\2018" "\2019"; 186 | } 187 | 188 | /** 189 | * Address inconsistent and variable font size in all browsers. 190 | */ 191 | 192 | small { 193 | font-size: 80%; 194 | } 195 | 196 | /** 197 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 198 | */ 199 | 200 | sub, 201 | sup { 202 | position: relative; 203 | vertical-align: baseline; 204 | font-size: 75%; 205 | line-height: 0; 206 | } 207 | 208 | sup { 209 | top: -0.5em; 210 | } 211 | 212 | sub { 213 | bottom: -0.25em; 214 | } 215 | 216 | /** 217 | * Define consistent border, margin, and padding. 218 | */ 219 | 220 | fieldset { 221 | margin: 0 2px; 222 | padding: 0.35em 0.625em 0.75em; 223 | border: 1px solid #c0c0c0; 224 | } 225 | 226 | /** 227 | * 1. Correct `color` not being inherited in IE 8/9. 228 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 229 | */ 230 | 231 | legend { 232 | padding: 0; /* 2 */ 233 | border: 0; /* 1 */ 234 | } 235 | 236 | /** 237 | * 1. Correct font family not being inherited in all browsers. 238 | * 2. Correct font size not being inherited in all browsers. 239 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. 240 | * 4. Remove any default :focus styles 241 | * 5. Make sure webkit font smoothing is being inherited 242 | * 6. Remove default gradient in Android Firefox / FirefoxOS 243 | */ 244 | 245 | button, 246 | input, 247 | select, 248 | textarea { 249 | margin: 0; /* 3 */ 250 | font-size: 100%; /* 2 */ 251 | font-family: inherit; /* 1 */ 252 | outline-offset: 0; /* 4 */ 253 | outline-style: none; /* 4 */ 254 | outline-width: 0; /* 4 */ 255 | -webkit-font-smoothing: inherit; /* 5 */ 256 | background-image: none; /* 6 */ 257 | } 258 | 259 | /** 260 | * Address Firefox 4+ setting `line-height` on `input` using `importnt` in 261 | * the UA stylesheet. 262 | */ 263 | 264 | button, 265 | input { 266 | line-height: normal; 267 | } 268 | 269 | /** 270 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 271 | * All other form control elements do not inherit `text-transform` values. 272 | * Correct `button` style inheritance in Chrome, Safari 5+, and IE 8+. 273 | * Correct `select` style inheritance in Firefox 4+ and Opera. 274 | */ 275 | 276 | button, 277 | select { 278 | text-transform: none; 279 | } 280 | 281 | /** 282 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 283 | * and `video` controls. 284 | * 2. Correct inability to style clickable `input` types in iOS. 285 | * 3. Improve usability and consistency of cursor style between image-type 286 | * `input` and others. 287 | */ 288 | 289 | button, 290 | html input[type="button"], /* 1 */ 291 | input[type="reset"], 292 | input[type="submit"] { 293 | cursor: pointer; /* 3 */ 294 | -webkit-appearance: button; /* 2 */ 295 | } 296 | 297 | /** 298 | * Re-set default cursor for disabled elements. 299 | */ 300 | 301 | button[disabled], 302 | html input[disabled] { 303 | cursor: default; 304 | } 305 | 306 | /** 307 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 308 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 309 | * (include `-moz` to future-proof). 310 | */ 311 | 312 | input[type="search"] { 313 | -webkit-box-sizing: content-box; /* 2 */ 314 | -moz-box-sizing: content-box; 315 | box-sizing: content-box; 316 | -webkit-appearance: textfield; /* 1 */ 317 | } 318 | 319 | /** 320 | * Remove inner padding and search cancel button in Safari 5 and Chrome 321 | * on OS X. 322 | */ 323 | 324 | input[type="search"]::-webkit-search-cancel-button, 325 | input[type="search"]::-webkit-search-decoration { 326 | -webkit-appearance: none; 327 | } 328 | 329 | /** 330 | * Remove inner padding and border in Firefox 4+. 331 | */ 332 | 333 | button::-moz-focus-inner, 334 | input::-moz-focus-inner { 335 | padding: 0; 336 | border: 0; 337 | } 338 | 339 | /** 340 | * 1. Remove default vertical scrollbar in IE 8/9. 341 | * 2. Improve readability and alignment in all browsers. 342 | */ 343 | 344 | textarea { 345 | overflow: auto; /* 1 */ 346 | vertical-align: top; /* 2 */ 347 | } 348 | 349 | 350 | img { 351 | -webkit-user-drag: none; 352 | } 353 | 354 | /* ========================================================================== 355 | Tables 356 | ========================================================================== */ 357 | 358 | /** 359 | * Remove most spacing between table cells. 360 | */ 361 | 362 | table { 363 | border-spacing: 0; 364 | border-collapse: collapse; 365 | } 366 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_scaffolding.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Scaffolding 4 | * -------------------------------------------------- 5 | */ 6 | 7 | *, 8 | *:before, 9 | *:after { 10 | @include box-sizing(border-box); 11 | } 12 | 13 | html { 14 | overflow: hidden; 15 | -ms-touch-action: pan-y; 16 | touch-action: pan-y; 17 | } 18 | 19 | body, 20 | .ionic-body { 21 | @include touch-callout(none); 22 | @include font-smoothing(antialiased); 23 | @include text-size-adjust(none); 24 | @include tap-highlight-transparent(); 25 | @include user-select(none); 26 | 27 | top: 0; 28 | right: 0; 29 | bottom: 0; 30 | left: 0; 31 | overflow: hidden; 32 | 33 | margin: 0; 34 | padding: 0; 35 | 36 | color: $base-color; 37 | word-wrap: break-word; 38 | font-size: $font-size-base; 39 | font-family: $font-family-base; 40 | line-height: $line-height-computed; 41 | text-rendering: optimizeLegibility; 42 | -webkit-backface-visibility: hidden; 43 | -webkit-user-drag: none; 44 | } 45 | 46 | body.grade-b, 47 | body.grade-c { 48 | // disable optimizeLegibility for low end devices 49 | text-rendering: auto; 50 | } 51 | 52 | .content { 53 | // used for content areas not using the content directive 54 | position: relative; 55 | } 56 | 57 | .scroll-content { 58 | position: absolute; 59 | top: 0; 60 | right: 0; 61 | bottom: 0; 62 | left: 0; 63 | overflow: hidden; 64 | 65 | // Hide the top border if any 66 | margin-top: -1px; 67 | 68 | // Prevents any distortion of lines 69 | padding-top:1px; 70 | 71 | width: auto; 72 | height: auto; 73 | } 74 | 75 | .scroll-content-false, 76 | .menu .scroll-content.scroll-content-false{ 77 | z-index: $z-index-scroll-content-false; 78 | } 79 | 80 | .scroll-view { 81 | position: relative; 82 | display: block; 83 | overflow: hidden; 84 | 85 | // Hide the top border if any 86 | margin-top: -1px; 87 | } 88 | 89 | /** 90 | * Scroll is the scroll view component available for complex and custom 91 | * scroll view functionality. 92 | */ 93 | .scroll { 94 | @include user-select(none); 95 | @include touch-callout(none); 96 | @include text-size-adjust(none); 97 | @include transform-origin(left, top); 98 | } 99 | 100 | // hide webkit scrollbars 101 | ::-webkit-scrollbar { 102 | display:none; 103 | } 104 | 105 | // Scroll bar styles 106 | .scroll-bar { 107 | position: absolute; 108 | z-index: $z-index-scroll-bar; 109 | } 110 | // hide the scroll-bar during animations 111 | .ng-animate .scroll-bar { 112 | visibility: hidden; 113 | } 114 | .scroll-bar-h { 115 | right: 2px; 116 | bottom: 3px; 117 | left: 2px; 118 | height: 3px; 119 | 120 | .scroll-bar-indicator { 121 | height: 100%; 122 | } 123 | } 124 | 125 | .scroll-bar-v { 126 | top: 2px; 127 | right: 3px; 128 | bottom: 2px; 129 | width: 3px; 130 | 131 | .scroll-bar-indicator { 132 | width: 100%; 133 | } 134 | } 135 | .scroll-bar-indicator { 136 | position: absolute; 137 | border-radius: 4px; 138 | background: rgba(0,0,0,0.3); 139 | opacity: 1; 140 | @include transition(opacity .3s linear); 141 | 142 | &.scroll-bar-fade-out { 143 | opacity: 0; 144 | } 145 | } 146 | .platform-android .scroll-bar-indicator { 147 | // android doesn't have rounded ends on scrollbar 148 | border-radius: 0; 149 | } 150 | .grade-b .scroll-bar-indicator, 151 | .grade-c .scroll-bar-indicator { 152 | // disable rgba background and border radius for low end devices 153 | background: #aaa; 154 | 155 | &.scroll-bar-fade-out { 156 | @include transition(none); 157 | } 158 | } 159 | 160 | @keyframes refresh-spin { 161 | 0% { transform: translate3d(0,0,0) rotate(0); } 162 | 100% { transform: translate3d(0,0,0) rotate(180deg); } 163 | } 164 | 165 | @-webkit-keyframes refresh-spin { 166 | 0% {-webkit-transform: translate3d(0,0,0) rotate(0); } 167 | 100% {-webkit-transform: translate3d(0,0,0) rotate(180deg); } 168 | } 169 | 170 | @keyframes refresh-spin-back { 171 | 0% { transform: translate3d(0,0,0) rotate(180deg); } 172 | 100% { transform: translate3d(0,0,0) rotate(0); } 173 | } 174 | 175 | @-webkit-keyframes refresh-spin-back { 176 | 0% {-webkit-transform: translate3d(0,0,0) rotate(180deg); } 177 | 100% {-webkit-transform: translate3d(0,0,0) rotate(0); } 178 | } 179 | 180 | // Scroll refresher (for pull to refresh) 181 | .scroll-refresher { 182 | position: absolute; 183 | top: -60px; 184 | right: 0; 185 | left: 0; 186 | overflow: hidden; 187 | margin: auto; 188 | height: 60px; 189 | 190 | .ionic-refresher-content { 191 | position: absolute; 192 | bottom: 15px; 193 | left: 0; 194 | width: 100%; 195 | color: $scroll-refresh-icon-color; 196 | text-align: center; 197 | 198 | font-size: 30px; 199 | 200 | .text-refreshing, 201 | .text-pulling { 202 | font-size: 16px; 203 | line-height: 16px; 204 | } 205 | &.ionic-refresher-with-text { 206 | bottom: 10px; 207 | } 208 | } 209 | 210 | .icon-refreshing, 211 | .icon-pulling { 212 | width: 100%; 213 | -webkit-backface-visibility: hidden; 214 | -webkit-transform-style: preserve-3d; 215 | backface-visibility: hidden; 216 | transform-style: preserve-3d; 217 | } 218 | .icon-pulling { 219 | @include animation-name(refresh-spin-back); 220 | @include animation-duration(200ms); 221 | @include animation-timing-function(linear); 222 | @include animation-fill-mode(none); 223 | -webkit-transform: translate3d(0,0,0) rotate(0deg); 224 | transform: translate3d(0,0,0) rotate(0deg); 225 | } 226 | .icon-refreshing, 227 | .text-refreshing { 228 | display: none; 229 | } 230 | .icon-refreshing { 231 | @include animation-duration(1.5s); 232 | } 233 | 234 | &.active { 235 | .icon-pulling:not(.pulling-rotation-disabled) { 236 | @include animation-name(refresh-spin); 237 | -webkit-transform: translate3d(0,0,0) rotate(-180deg); 238 | transform: translate3d(0,0,0) rotate(-180deg); 239 | } 240 | &.refreshing { 241 | @include transition(transform .2s); 242 | @include transition(-webkit-transform .2s); 243 | -webkit-transform: scale(1,1); 244 | transform: scale(1,1); 245 | .icon-pulling, 246 | .text-pulling { 247 | display: none; 248 | } 249 | .icon-refreshing, 250 | .text-refreshing { 251 | display: block; 252 | } 253 | &.refreshing-tail{ 254 | -webkit-transform: scale(0,0); 255 | transform: scale(0,0); 256 | } 257 | } 258 | } 259 | } 260 | 261 | ion-infinite-scroll { 262 | height: 60px; 263 | width: 100%; 264 | opacity: 0; 265 | display: block; 266 | 267 | @include transition(opacity 0.25s); 268 | @include display-flex(); 269 | @include flex-direction(row); 270 | @include justify-content(center); 271 | @include align-items(center); 272 | 273 | .icon { 274 | color: #666666; 275 | font-size: 30px; 276 | color: $scroll-refresh-icon-color; 277 | } 278 | 279 | &.active { 280 | opacity: 1; 281 | } 282 | } 283 | 284 | .overflow-scroll { 285 | overflow-x: hidden; 286 | overflow-y: scroll; 287 | -webkit-overflow-scrolling: touch; 288 | top: 0; 289 | right: 0; 290 | bottom: 0; 291 | left: 0; 292 | position: absolute; 293 | 294 | .scroll { 295 | position: static; 296 | height: 100%; 297 | -webkit-transform: translate3d(0, 0, 0); // fix iOS bug where relative children of scroller disapear while scrolling. see: http://stackoverflow.com/questions/9807620/ipad-safari-scrolling-causes-html-elements-to-disappear-and-reappear-with-a-dela 298 | } 299 | } 300 | 301 | 302 | // Pad top/bottom of content so it doesn't hide behind .bar-title and .bar-tab. 303 | // Note: For these to work, content must come after both bars in the markup 304 | /* If you change these, change platform.scss as well */ 305 | .has-header { 306 | top: $bar-height; 307 | } 308 | // Force no header 309 | .no-header { 310 | top: 0; 311 | } 312 | 313 | .has-subheader { 314 | top: $bar-height + $bar-subheader-height; 315 | } 316 | .has-tabs-top { 317 | top: $bar-height + $tabs-height; 318 | } 319 | .has-header.has-subheader.has-tabs-top { 320 | top: $bar-height + $bar-subheader-height + $tabs-height; 321 | } 322 | 323 | .has-footer { 324 | bottom: $bar-footer-height; 325 | } 326 | .has-subfooter { 327 | bottom: $bar-footer-height + $bar-subfooter-height; 328 | } 329 | 330 | .has-tabs, 331 | .bar-footer.has-tabs { 332 | bottom: $tabs-height; 333 | } 334 | 335 | .has-footer.has-tabs { 336 | bottom: $tabs-height + $bar-footer-height; 337 | } 338 | 339 | // A full screen section with a solid background 340 | .pane { 341 | @include translate3d(0,0,0); 342 | @include transition-duration(0); 343 | z-index: $z-index-pane; 344 | } 345 | .view { 346 | z-index: $z-index-view; 347 | } 348 | .pane, 349 | .view { 350 | position: absolute; 351 | top: 0; 352 | right: 0; 353 | bottom: 0; 354 | left: 0; 355 | width: 100%; 356 | height: 100%; 357 | background-color: $base-background-color; 358 | overflow: hidden; 359 | } 360 | .view-container { 361 | position: absolute; 362 | display: block; 363 | width: 100%; 364 | height: 100%; 365 | } 366 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_bar.scss: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Bar (Headers and Footers) 4 | * -------------------------------------------------- 5 | */ 6 | 7 | .bar { 8 | @include display-flex(); 9 | @include translate3d(0,0,0); 10 | @include user-select(none); 11 | position: absolute; 12 | right: 0; 13 | left: 0; 14 | z-index: $z-index-bar; 15 | 16 | box-sizing: border-box; 17 | padding: $bar-padding-portrait; 18 | 19 | width: 100%; 20 | height: $bar-height; 21 | border-width: 0; 22 | border-style: solid; 23 | border-top: 1px solid transparent; 24 | border-bottom: 1px solid $bar-default-border; 25 | 26 | background-color: $bar-default-bg; 27 | 28 | /* border-width: 1px will actually create 2 device pixels on retina */ 29 | /* this nifty trick sets an actual 1px border on hi-res displays */ 30 | background-size: 0; 31 | @media (min--moz-device-pixel-ratio: 1.5), 32 | (-webkit-min-device-pixel-ratio: 1.5), 33 | (min-device-pixel-ratio: 1.5), 34 | (min-resolution: 144dpi), 35 | (min-resolution: 1.5dppx) { 36 | border: none; 37 | background-image: linear-gradient(0deg, $bar-default-border, $bar-default-border 50%, transparent 50%); 38 | background-position: bottom; 39 | background-size: 100% 1px; 40 | background-repeat: no-repeat; 41 | } 42 | 43 | &.bar-clear { 44 | border: none; 45 | background: none; 46 | color: #fff; 47 | 48 | .button { 49 | color: #fff; 50 | } 51 | .title { 52 | color: #fff; 53 | } 54 | } 55 | 56 | &.item-input-inset { 57 | .item-input-wrapper { 58 | margin-top: -1px; 59 | 60 | input { 61 | padding-left: 8px; 62 | width: 94%; 63 | height: 28px; 64 | background: transparent; 65 | } 66 | } 67 | } 68 | 69 | &.bar-light { 70 | @include bar-style($bar-light-bg, $bar-light-border, $bar-light-text); 71 | &.bar-footer{ 72 | background-image: linear-gradient(180deg, $bar-light-border, $bar-light-border 50%, transparent 50%); 73 | } 74 | } 75 | &.bar-stable { 76 | @include bar-style($bar-stable-bg, $bar-stable-border, $bar-stable-text); 77 | &.bar-footer{ 78 | background-image: linear-gradient(180deg, $bar-stable-border, $bar-stable-border 50%, transparent 50%); 79 | } 80 | } 81 | &.bar-positive { 82 | @include bar-style($bar-positive-bg, $bar-positive-border, $bar-positive-text); 83 | &.bar-footer{ 84 | background-image: linear-gradient(180deg, $bar-positive-border, $bar-positive-border 50%, transparent 50%); 85 | } 86 | } 87 | &.bar-calm { 88 | @include bar-style($bar-calm-bg, $bar-calm-border, $bar-calm-text); 89 | &.bar-footer{ 90 | background-image: linear-gradient(180deg, $bar-calm-border, $bar-calm-border 50%, transparent 50%); 91 | } 92 | } 93 | &.bar-assertive { 94 | @include bar-style($bar-assertive-bg, $bar-assertive-border, $bar-assertive-text); 95 | &.bar-footer{ 96 | background-image: linear-gradient(180deg, $bar-assertive-border, $bar-assertive-border 50%, transparent 50%); 97 | } 98 | } 99 | &.bar-balanced { 100 | @include bar-style($bar-balanced-bg, $bar-balanced-border, $bar-balanced-text); 101 | &.bar-footer{ 102 | background-image: linear-gradient(180deg, $bar-balanced-border, $bar-positive-border 50%, transparent 50%); 103 | } 104 | } 105 | &.bar-energized { 106 | @include bar-style($bar-energized-bg, $bar-energized-border, $bar-energized-text); 107 | &.bar-footer{ 108 | background-image: linear-gradient(180deg, $bar-energized-border, $bar-energized-border 50%, transparent 50%); 109 | } 110 | } 111 | &.bar-royal { 112 | @include bar-style($bar-royal-bg, $bar-royal-border, $bar-royal-text); 113 | &.bar-footer{ 114 | background-image: linear-gradient(180deg, $bar-royal-border, $bar-royal-border 50%, transparent 50%); 115 | } 116 | } 117 | &.bar-dark { 118 | @include bar-style($bar-dark-bg, $bar-dark-border, $bar-dark-text); 119 | &.bar-footer{ 120 | background-image: linear-gradient(180deg, $bar-dark-border, $bar-dark-border 50%, transparent 50%); 121 | } 122 | } 123 | 124 | // Title inside of a bar is centered 125 | .title { 126 | position: absolute; 127 | 128 | top: 0; 129 | right: 0; 130 | left: 0; 131 | z-index: $z-index-bar-title; 132 | overflow: hidden; 133 | 134 | margin: 0 10px; 135 | 136 | min-width: 30px; 137 | height: $bar-height - 1; 138 | 139 | text-align: center; 140 | 141 | // Go into ellipsis if too small 142 | text-overflow: ellipsis; 143 | white-space: nowrap; 144 | 145 | font-size: $bar-title-font-size; 146 | font-weight: $headings-font-weight; 147 | 148 | line-height: $bar-height; 149 | 150 | &.title-left { 151 | text-align: left; 152 | } 153 | &.title-right { 154 | text-align: right; 155 | } 156 | } 157 | 158 | .title a { 159 | color: inherit; 160 | } 161 | 162 | .button { 163 | z-index: $z-index-bar-button; 164 | padding: 0 $button-bar-button-padding; 165 | min-width: initial; 166 | min-height: $button-bar-button-height - 1; 167 | font-weight: 400; 168 | font-size: $button-bar-button-font-size; 169 | line-height: $button-bar-button-height; 170 | 171 | &.button-icon:before, 172 | .icon:before, 173 | &.icon:before, 174 | &.icon-left:before, 175 | &.icon-right:before { 176 | padding-right: 2px; 177 | padding-left: 2px; 178 | font-size: $button-bar-button-icon-size; 179 | line-height: $button-bar-button-height; 180 | } 181 | 182 | &.button-icon { 183 | font-size: $bar-title-font-size; 184 | .icon:before, 185 | &:before, 186 | &.icon-left:before, 187 | &.icon-right:before { 188 | vertical-align: top; 189 | font-size: $button-large-icon-size; 190 | line-height: $button-bar-button-height; 191 | } 192 | } 193 | &.button-clear { 194 | padding-right: 2px; 195 | padding-left: 2px; 196 | font-weight: 300; 197 | font-size: $bar-title-font-size; 198 | 199 | .icon:before, 200 | &.icon:before, 201 | &.icon-left:before, 202 | &.icon-right:before { 203 | font-size: $button-large-icon-size; 204 | line-height: $button-bar-button-height; 205 | } 206 | } 207 | 208 | &.back-button { 209 | display: block; 210 | margin-right: 5px; 211 | padding: 0; 212 | white-space: nowrap; 213 | font-weight: 400; 214 | } 215 | 216 | &.back-button.active, 217 | &.back-button.activated { 218 | opacity: 0.2; 219 | } 220 | } 221 | 222 | .button-bar > .button, 223 | .buttons > .button { 224 | min-height: $button-bar-button-height - 1; 225 | line-height: $button-bar-button-height; 226 | } 227 | 228 | .button-bar + .button, 229 | .button + .button-bar { 230 | margin-left: 5px; 231 | } 232 | 233 | // Android 4.4 messes with the display property 234 | .buttons, 235 | .buttons.primary-buttons, 236 | .buttons.secondary-buttons { 237 | display: inherit; 238 | } 239 | .buttons span { 240 | display: inline-block; 241 | } 242 | .buttons-left span { 243 | margin-right: 5px; 244 | } 245 | .buttons-right span { 246 | margin-left: 5px; 247 | } 248 | 249 | // Place the last button in a bar on the right of the bar 250 | .title + .button:last-child, 251 | > .button + .button:last-child, 252 | > .button.pull-right, 253 | .buttons.pull-right, 254 | .title + .buttons { 255 | position: absolute; 256 | top: 5px; 257 | right: 5px; 258 | bottom: 5px; 259 | } 260 | 261 | } 262 | 263 | // Default styles for buttons inside of styled bars 264 | .bar-light { 265 | .button { 266 | @include button-style($bar-light-bg, $bar-light-border, $bar-light-active-bg, $bar-light-active-border, $bar-light-text); 267 | @include button-clear($bar-light-text, $bar-title-font-size); 268 | } 269 | } 270 | .bar-stable { 271 | .button { 272 | @include button-style($bar-stable-bg, $bar-stable-border, $bar-stable-active-bg, $bar-stable-active-border, $bar-stable-text); 273 | @include button-clear($bar-stable-text, $bar-title-font-size); 274 | } 275 | } 276 | .bar-positive { 277 | .button { 278 | @include button-style($bar-positive-bg, $bar-positive-border, $bar-positive-active-bg, $bar-positive-active-border, $bar-positive-text); 279 | @include button-clear(#fff, $bar-title-font-size); 280 | } 281 | } 282 | .bar-calm { 283 | .button { 284 | @include button-style($bar-calm-bg, $bar-calm-border, $bar-calm-active-bg, $bar-calm-active-border, $bar-calm-text); 285 | @include button-clear(#fff, $bar-title-font-size); 286 | } 287 | } 288 | .bar-assertive { 289 | .button { 290 | @include button-style($bar-assertive-bg, $bar-assertive-border, $bar-assertive-active-bg, $bar-assertive-active-border, $bar-assertive-text); 291 | @include button-clear(#fff, $bar-title-font-size); 292 | } 293 | } 294 | .bar-balanced { 295 | .button { 296 | @include button-style($bar-balanced-bg, $bar-balanced-border, $bar-balanced-active-bg, $bar-balanced-active-border, $bar-balanced-text); 297 | @include button-clear(#fff, $bar-title-font-size); 298 | } 299 | } 300 | .bar-energized { 301 | .button { 302 | @include button-style($bar-energized-bg, $bar-energized-border, $bar-energized-active-bg, $bar-energized-active-border, $bar-energized-text); 303 | @include button-clear(#fff, $bar-title-font-size); 304 | } 305 | } 306 | .bar-royal { 307 | .button { 308 | @include button-style($bar-royal-bg, $bar-royal-border, $bar-royal-active-bg, $bar-royal-active-border, $bar-royal-text); 309 | @include button-clear(#fff, $bar-title-font-size); 310 | } 311 | } 312 | .bar-dark { 313 | .button { 314 | @include button-style($bar-dark-bg, $bar-dark-border, $bar-dark-active-bg, $bar-dark-active-border, $bar-dark-text); 315 | @include button-clear(#fff, $bar-title-font-size); 316 | } 317 | } 318 | 319 | // Header at top 320 | .bar-header { 321 | top: 0; 322 | border-top-width: 0; 323 | border-bottom-width: 1px; 324 | &.has-tabs-top{ 325 | border-bottom-width: 0px; 326 | background-image: none; 327 | } 328 | } 329 | 330 | // Footer at bottom 331 | .bar-footer { 332 | bottom: 0; 333 | border-top-width: 1px; 334 | border-bottom-width: 0; 335 | background-position: top; 336 | 337 | height: $bar-footer-height; 338 | 339 | &.item-input-inset { 340 | position: absolute; 341 | } 342 | } 343 | 344 | // Don't render padding if the bar is just for tabs 345 | .bar-tabs { 346 | padding: 0; 347 | } 348 | 349 | .bar-subheader { 350 | top: $bar-height; 351 | display: block; 352 | 353 | height: $bar-subheader-height; 354 | } 355 | .bar-subfooter { 356 | bottom: $bar-footer-height; 357 | display: block; 358 | 359 | height: $bar-subfooter-height; 360 | } 361 | 362 | .nav-bar-block { 363 | position: absolute; 364 | top: 0; 365 | right: 0; 366 | left: 0; 367 | z-index: $z-index-bar; 368 | } 369 | 370 | .bar .back-button.hide, 371 | .bar .buttons .hide { 372 | display: none; 373 | } 374 | -------------------------------------------------------------------------------- /www/js/app.js: -------------------------------------------------------------------------------- 1 | var cipherSafe = angular.module("ciphersafe", ["ionic", "firebase"]); 2 | var fb = new Firebase("https://INSTANCE_ID_HERE.firebaseio.com/"); 3 | 4 | cipherSafe.run(function($ionicPlatform, $state) { 5 | $ionicPlatform.ready(function() { 6 | if(window.cordova && window.cordova.plugins.Keyboard) { 7 | cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); 8 | } 9 | if(window.StatusBar) { 10 | StatusBar.styleDefault(); 11 | } 12 | }); 13 | document.addEventListener("resume", function() { 14 | $state.go("locked", {}, {location: "replace"}); 15 | }, false); 16 | }); 17 | 18 | cipherSafe.config(function($stateProvider, $urlRouterProvider) { 19 | $stateProvider 20 | .state("locked", { 21 | url: "/locked", 22 | templateUrl: "templates/locked.html", 23 | controller: "VaultController", 24 | cache: false 25 | }) 26 | .state("createvault", { 27 | url: "/createvault", 28 | templateUrl: "templates/create_vault.html", 29 | controller: "VaultController" 30 | }) 31 | .state("firebase", { 32 | url: "/firebase", 33 | templateUrl: "templates/firebase.html", 34 | controller: "FirebaseController" 35 | }) 36 | .state("categories", { 37 | url: "/categories/:masterPassword", 38 | templateUrl: "templates/categories.html", 39 | controller: "CategoryController" 40 | }) 41 | .state("passwords", { 42 | url: "/passwords/:categoryId/:masterPassword", 43 | templateUrl: "templates/password_list.html", 44 | controller: "PasswordController", 45 | cache: false 46 | }) 47 | .state("newpassword", { 48 | url: "/newpassword/:categoryId/:masterPassword", 49 | templateUrl: "templates/password_new.html", 50 | controller: "PasswordController" 51 | }) 52 | .state("viewpassword", { 53 | url: "/viewpassword/:categoryId/:masterPassword/:passwordId", 54 | templateUrl: "templates/password_view.html", 55 | controller: "PasswordController" 56 | }); 57 | $urlRouterProvider.otherwise('/locked'); 58 | }); 59 | 60 | 61 | cipherSafe.controller("VaultController", function($scope, $state, $ionicHistory, $firebaseObject, $cipherFactory) { 62 | 63 | $ionicHistory.clearHistory(); 64 | 65 | $ionicHistory.nextViewOptions({ 66 | disableAnimate: true, 67 | disableBack: true 68 | }); 69 | 70 | var fbAuth = fb.getAuth(); 71 | if(fbAuth) { 72 | var userReference = fb.child("users/" + fbAuth.uid); 73 | var syncObject = $firebaseObject(userReference); 74 | syncObject.$bindTo($scope, "data"); 75 | } else { 76 | $state.go("firebase"); 77 | } 78 | 79 | $scope.unlock = function(masterPassword) { 80 | syncObject.$loaded().then(function() { 81 | var decipherPhrase = $cipherFactory.decrypt($scope.data.masterPassword.cipher_text, masterPassword, $scope.data.masterPassword.salt, $scope.data.masterPassword.iv, {output: "hex"}); 82 | if(decipherPhrase === "Authenticated".toHex()) { 83 | $state.go("categories", {masterPassword: masterPassword}); 84 | } 85 | }); 86 | } 87 | 88 | $scope.create = function(masterPassword) { 89 | syncObject.$loaded().then(function() { 90 | userReference.child("masterPassword").set($cipherFactory.encrypt("Authenticated", masterPassword), function(error) { 91 | $state.go("locked"); 92 | }); 93 | }); 94 | } 95 | 96 | $scope.reset = function() { 97 | userReference.remove(function(error) { 98 | if(error) { 99 | console.error("ERROR: " + error); 100 | } else { 101 | $state.go("createvault"); 102 | } 103 | }); 104 | } 105 | 106 | }); 107 | 108 | cipherSafe.controller("FirebaseController", function($scope, $state, $ionicHistory, $firebaseAuth) { 109 | 110 | $ionicHistory.nextViewOptions({ 111 | disableAnimate: true, 112 | disableBack: true 113 | }); 114 | 115 | var fbAuth = $firebaseAuth(fb); 116 | 117 | $scope.login = function(username, password) { 118 | fbAuth.$authWithPassword({ 119 | email: username, 120 | password: password 121 | }).then(function(authData) { 122 | $state.go("locked"); 123 | }).catch(function(error) { 124 | console.error("ERROR: " + error); 125 | }); 126 | } 127 | 128 | $scope.register = function(username, password) { 129 | fbAuth.$createUser({email: username, password: password}).then(function(userData) { 130 | return fbAuth.$authWithPassword({ 131 | email: username, 132 | password: password 133 | }); 134 | }).then(function(authData) { 135 | $state.go("createvault"); 136 | }).catch(function(error) { 137 | console.error("ERROR: " + error); 138 | }); 139 | } 140 | 141 | }); 142 | 143 | cipherSafe.controller("CategoryController", function($scope, $ionicPopup, $firebaseObject, $stateParams, $cipherFactory) { 144 | 145 | $scope.masterPassword = $stateParams.masterPassword; 146 | $scope.categories = []; 147 | 148 | var fbAuth = fb.getAuth(); 149 | if(fbAuth) { 150 | var categoriesReference = fb.child("users/" + fbAuth.uid); 151 | var syncObject = $firebaseObject(categoriesReference); 152 | syncObject.$bindTo($scope, "data"); 153 | } else { 154 | $state.go("firebase"); 155 | } 156 | 157 | $scope.list = function() { 158 | syncObject.$loaded().then(function() { 159 | for(var key in $scope.data.categories) { 160 | if($scope.data.categories.hasOwnProperty(key)) { 161 | $scope.categories.push({ 162 | id: key, 163 | category: $cipherFactory.decrypt($scope.data.categories[key].category.cipher_text, $stateParams.masterPassword, $scope.data.categories[key].category.salt, $scope.data.categories[key].category.iv) 164 | }); 165 | } 166 | } 167 | }); 168 | } 169 | 170 | $scope.add = function() { 171 | $ionicPopup.prompt({ 172 | title: 'Enter a new category', 173 | inputType: 'text' 174 | }) 175 | .then(function(result) { 176 | if(result !== undefined) { 177 | if($scope.data.categories === undefined) { 178 | $scope.data.categories = {}; 179 | } 180 | if($scope.data.categories[result.toSHA1()] === undefined) { 181 | $scope.data.categories[result.toSHA1()] = { 182 | category: $cipherFactory.encrypt(result, $stateParams.masterPassword), 183 | passwords: {} 184 | }; 185 | $scope.categories.push({ 186 | id: result.toSHA1(), 187 | category: result 188 | }); 189 | } 190 | } else { 191 | console.log("Action not completed"); 192 | } 193 | }); 194 | } 195 | 196 | }); 197 | 198 | cipherSafe.controller("PasswordController", function($scope, $stateParams, $firebaseObject, $state, $cipherFactory, $ionicHistory) { 199 | 200 | $scope.masterPassword = $stateParams.masterPassword; 201 | $scope.categoryId = $stateParams.categoryId; 202 | $scope.passwords = []; 203 | 204 | var fbAuth = fb.getAuth(); 205 | if(fbAuth) { 206 | var categoryReference = fb.child("users/" + fbAuth.uid + "/categories/" + $stateParams.categoryId); 207 | var passwordsReference = fb.child("users/" + fbAuth.uid + "/categories/" + $stateParams.categoryId + "/passwords"); 208 | var syncObject = $firebaseObject(categoryReference); 209 | syncObject.$bindTo($scope, "data"); 210 | } else { 211 | $state.go("firebase"); 212 | } 213 | 214 | $scope.list = function() { 215 | syncObject.$loaded().then(function() { 216 | var encryptedPasswords = $scope.data.passwords; 217 | for(var key in encryptedPasswords) { 218 | if(encryptedPasswords.hasOwnProperty(key)) { 219 | $scope.passwords.push({ 220 | id: key, 221 | password: JSON.parse($cipherFactory.decrypt(encryptedPasswords[key].cipher_text, $stateParams.masterPassword, encryptedPasswords[key].salt, encryptedPasswords[key].iv)) 222 | }); 223 | } 224 | } 225 | }); 226 | } 227 | 228 | $scope.view = function() { 229 | syncObject.$loaded().then(function() { 230 | var encryptedPassword = $scope.data.passwords[$stateParams.passwordId]; 231 | $scope.password = JSON.parse($cipherFactory.decrypt(encryptedPassword.cipher_text, $stateParams.masterPassword, encryptedPassword.salt, encryptedPassword.iv)); 232 | }); 233 | } 234 | 235 | $scope.save = function(title, username, password) { 236 | var passwordObject = { 237 | title: title, 238 | username: username, 239 | password: password 240 | }; 241 | syncObject.$loaded().then(function() { 242 | passwordsReference.child(JSON.stringify(passwordObject).toSHA1()).set($cipherFactory.encrypt(JSON.stringify(passwordObject), $stateParams.masterPassword), function(ref) { 243 | $state.go("passwords", $stateParams); 244 | }); 245 | }); 246 | } 247 | 248 | $scope.back = function() { 249 | $ionicHistory.goBack(); 250 | } 251 | 252 | }); 253 | 254 | 255 | cipherSafe.factory("$cipherFactory", function() { 256 | 257 | return { 258 | 259 | /* 260 | * Encrypt a message with a passphrase or password 261 | * 262 | * @param string message 263 | * @param string password 264 | * @return object 265 | */ 266 | encrypt: function(message, password) { 267 | var salt = forge.random.getBytesSync(128); 268 | var key = forge.pkcs5.pbkdf2(password, salt, 4, 16); 269 | var iv = forge.random.getBytesSync(16); 270 | var cipher = forge.cipher.createCipher('AES-CBC', key); 271 | cipher.start({iv: iv}); 272 | cipher.update(forge.util.createBuffer(message)); 273 | cipher.finish(); 274 | var cipherText = forge.util.encode64(cipher.output.getBytes()); 275 | return {cipher_text: cipherText, salt: forge.util.encode64(salt), iv: forge.util.encode64(iv)}; 276 | }, 277 | 278 | /* 279 | * Decrypt cipher text using a password or passphrase and a corresponding salt and iv 280 | * 281 | * @param string (Base64) cipherText 282 | * @param string password 283 | * @param string (Base64) salt 284 | * @param string (Base64) iv 285 | * @param object 286 | * @return string 287 | */ 288 | decrypt: function(cipherText, password, salt, iv, options) { 289 | var key = forge.pkcs5.pbkdf2(password, forge.util.decode64(salt), 4, 16); 290 | var decipher = forge.cipher.createDecipher('AES-CBC', key); 291 | decipher.start({iv: forge.util.decode64(iv)}); 292 | decipher.update(forge.util.createBuffer(forge.util.decode64(cipherText))); 293 | decipher.finish(); 294 | if(options !== undefined && options.hasOwnProperty("output") && options.output === "hex") { 295 | return decipher.output.toHex(); 296 | } else { 297 | return decipher.output.toString(); 298 | } 299 | } 300 | 301 | }; 302 | 303 | }); 304 | 305 | String.prototype.toHex = function() { 306 | var buffer = forge.util.createBuffer(this.toString()); 307 | return buffer.toHex(); 308 | } 309 | 310 | String.prototype.toSHA1 = function() { 311 | var md = forge.md.sha1.create(); 312 | md.update(this); 313 | return md.digest().toHex(); 314 | } 315 | -------------------------------------------------------------------------------- /www/lib/ionic/js/angular/angular-animate.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.6 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(N,f,W){'use strict';f.module("ngAnimate",["ng"]).directive("ngAnimateChildren",function(){return function(X,C,g){g=g.ngAnimateChildren;f.isString(g)&&0===g.length?C.data("$$ngAnimateChildren",!0):X.$watch(g,function(f){C.data("$$ngAnimateChildren",!!f)})}}).factory("$$animateReflow",["$$rAF","$document",function(f,C){return function(g){return f(function(){g()})}}]).config(["$provide","$animateProvider",function(X,C){function g(f){for(var n=0;n=C&&b>=x&&c()}var m=g(e);a=e.data("$$ngAnimateCSS3Data");if(-1!=m.getAttribute("class").indexOf(b)&&a){var k="",t="";n(b.split(" "),function(a, 26 | b){var e=(0 .tabs, 46 | .tabs.tabs-light { 47 | @include tab-style($tabs-light-bg, $tabs-light-border, $tabs-light-text); 48 | @include tab-badge-style($tabs-light-text, $tabs-light-bg); 49 | } 50 | .tabs-stable > .tabs, 51 | .tabs.tabs-stable { 52 | @include tab-style($tabs-stable-bg, $tabs-stable-border, $tabs-stable-text); 53 | @include tab-badge-style($tabs-stable-text, $tabs-stable-bg); 54 | } 55 | .tabs-positive > .tabs, 56 | .tabs.tabs-positive { 57 | @include tab-style($tabs-positive-bg, $tabs-positive-border, $tabs-positive-text); 58 | @include tab-badge-style($tabs-positive-text, $tabs-positive-bg); 59 | } 60 | .tabs-calm > .tabs, 61 | .tabs.tabs-calm { 62 | @include tab-style($tabs-calm-bg, $tabs-calm-border, $tabs-calm-text); 63 | @include tab-badge-style($tabs-calm-text, $tabs-calm-bg); 64 | } 65 | .tabs-assertive > .tabs, 66 | .tabs.tabs-assertive { 67 | @include tab-style($tabs-assertive-bg, $tabs-assertive-border, $tabs-assertive-text); 68 | @include tab-badge-style($tabs-assertive-text, $tabs-assertive-bg); 69 | } 70 | .tabs-balanced > .tabs, 71 | .tabs.tabs-balanced { 72 | @include tab-style($tabs-balanced-bg, $tabs-balanced-border, $tabs-balanced-text); 73 | @include tab-badge-style($tabs-balanced-text, $tabs-balanced-bg); 74 | } 75 | .tabs-energized > .tabs, 76 | .tabs.tabs-energized { 77 | @include tab-style($tabs-energized-bg, $tabs-energized-border, $tabs-energized-text); 78 | @include tab-badge-style($tabs-energized-text, $tabs-energized-bg); 79 | } 80 | .tabs-royal > .tabs, 81 | .tabs.tabs-royal { 82 | @include tab-style($tabs-royal-bg, $tabs-royal-border, $tabs-royal-text); 83 | @include tab-badge-style($tabs-royal-text, $tabs-royal-bg); 84 | } 85 | .tabs-dark > .tabs, 86 | .tabs.tabs-dark { 87 | @include tab-style($tabs-dark-bg, $tabs-dark-border, $tabs-dark-text); 88 | @include tab-badge-style($tabs-dark-text, $tabs-dark-bg); 89 | } 90 | 91 | @mixin tabs-striped($style, $color, $background) { 92 | &.#{$style} { 93 | .tabs{ 94 | background-color: $background; 95 | } 96 | .tab-item { 97 | color: rgba($color, $tabs-striped-off-opacity); 98 | opacity: 1; 99 | .badge{ 100 | opacity:$tabs-striped-off-opacity; 101 | } 102 | &.tab-item-active, 103 | &.active, 104 | &.activated { 105 | margin-top: -$tabs-striped-border-width; 106 | color: $color; 107 | border-style: solid; 108 | border-width: $tabs-striped-border-width 0 0 0; 109 | border-color: $color; 110 | } 111 | } 112 | } 113 | &.tabs-top{ 114 | .tab-item { 115 | &.tab-item-active, 116 | &.active, 117 | &.activated { 118 | .badge { 119 | top: 4%; 120 | } 121 | } 122 | } 123 | } 124 | } 125 | 126 | @mixin tabs-background($style, $color, $border-color) { 127 | .#{$style} { 128 | .tabs, 129 | &> .tabs{ 130 | background-color: $color; 131 | background-image: linear-gradient(0deg, $border-color, $border-color 50%, transparent 50%); 132 | border-color: $border-color; 133 | } 134 | } 135 | } 136 | 137 | @mixin tabs-striped-background($style, $color) { 138 | &.#{$style} { 139 | .tabs { 140 | background-color: $color; 141 | background-image:none; 142 | } 143 | } 144 | } 145 | 146 | @mixin tabs-color($style, $color) { 147 | .#{$style} { 148 | .tab-item { 149 | color: rgba($color, $tabs-off-opacity); 150 | opacity: 1; 151 | .badge{ 152 | opacity:$tabs-off-opacity; 153 | } 154 | &.tab-item-active, 155 | &.active, 156 | &.activated { 157 | color: $color; 158 | border: 0 solid $color; 159 | .badge{ 160 | opacity: 1; 161 | } 162 | } 163 | } 164 | } 165 | } 166 | 167 | @mixin tabs-striped-color($style, $color) { 168 | &.#{$style} { 169 | .tab-item { 170 | color: rgba($color, $tabs-striped-off-opacity); 171 | opacity: 1; 172 | .badge{ 173 | opacity:$tabs-striped-off-opacity; 174 | } 175 | &.tab-item-active, 176 | &.active, 177 | &.activated { 178 | margin-top: -$tabs-striped-border-width; 179 | color: $color; 180 | border: 0 solid $color; 181 | border-top-width: $tabs-striped-border-width; 182 | .badge{ 183 | top:$tabs-striped-border-width; 184 | opacity: 1; 185 | } 186 | } 187 | } 188 | } 189 | } 190 | 191 | .tabs-striped { 192 | .tabs { 193 | background-color: white; 194 | background-image: none; 195 | border: none; 196 | border-bottom: 1px solid #ddd; 197 | padding-top: $tabs-striped-border-width; 198 | } 199 | .tab-item { 200 | // default android tab style 201 | &.tab-item-active, 202 | &.active, 203 | &.activated { 204 | margin-top: -$tabs-striped-border-width; 205 | border-style: solid; 206 | border-width: $tabs-striped-border-width 0 0 0; 207 | border-color: $dark; 208 | .badge{ 209 | top:$tabs-striped-border-width; 210 | opacity: 1; 211 | } 212 | } 213 | } 214 | @include tabs-striped('tabs-light', $dark, $light); 215 | @include tabs-striped('tabs-stable', $dark, $stable); 216 | @include tabs-striped('tabs-positive', $light, $positive); 217 | @include tabs-striped('tabs-calm', $light, $calm); 218 | @include tabs-striped('tabs-assertive', $light, $assertive); 219 | @include tabs-striped('tabs-balanced', $light, $balanced); 220 | @include tabs-striped('tabs-energized', $light, $energized); 221 | @include tabs-striped('tabs-royal', $light, $royal); 222 | @include tabs-striped('tabs-dark', $light, $dark); 223 | 224 | // doing this twice so striped tabs styles don't override specific bg and color vals 225 | @include tabs-striped-background('tabs-background-light', $light); 226 | @include tabs-striped-background('tabs-background-stable', $stable); 227 | @include tabs-striped-background('tabs-background-positive', $positive); 228 | @include tabs-striped-background('tabs-background-calm', $calm); 229 | @include tabs-striped-background('tabs-background-assertive', $assertive); 230 | @include tabs-striped-background('tabs-background-balanced', $balanced); 231 | @include tabs-striped-background('tabs-background-energized',$energized); 232 | @include tabs-striped-background('tabs-background-royal', $royal); 233 | @include tabs-striped-background('tabs-background-dark', $dark); 234 | 235 | @include tabs-striped-color('tabs-color-light', $light); 236 | @include tabs-striped-color('tabs-color-stable', $stable); 237 | @include tabs-striped-color('tabs-color-positive', $positive); 238 | @include tabs-striped-color('tabs-color-calm', $calm); 239 | @include tabs-striped-color('tabs-color-assertive', $assertive); 240 | @include tabs-striped-color('tabs-color-balanced', $balanced); 241 | @include tabs-striped-color('tabs-color-energized',$energized); 242 | @include tabs-striped-color('tabs-color-royal', $royal); 243 | @include tabs-striped-color('tabs-color-dark', $dark); 244 | 245 | } 246 | 247 | @include tabs-background('tabs-background-light', $light, $bar-light-border); 248 | @include tabs-background('tabs-background-stable', $stable, $bar-stable-border); 249 | @include tabs-background('tabs-background-positive', $positive, $bar-positive-border); 250 | @include tabs-background('tabs-background-calm', $calm, $bar-calm-border); 251 | @include tabs-background('tabs-background-assertive', $assertive, $bar-assertive-border); 252 | @include tabs-background('tabs-background-balanced', $balanced, $bar-balanced-border); 253 | @include tabs-background('tabs-background-energized',$energized, $bar-energized-border); 254 | @include tabs-background('tabs-background-royal', $royal, $bar-royal-border); 255 | @include tabs-background('tabs-background-dark', $dark, $bar-dark-border); 256 | 257 | @include tabs-color('tabs-color-light', $light); 258 | @include tabs-color('tabs-color-stable', $stable); 259 | @include tabs-color('tabs-color-positive', $positive); 260 | @include tabs-color('tabs-color-calm', $calm); 261 | @include tabs-color('tabs-color-assertive', $assertive); 262 | @include tabs-color('tabs-color-balanced', $balanced); 263 | @include tabs-color('tabs-color-energized',$energized); 264 | @include tabs-color('tabs-color-royal', $royal); 265 | @include tabs-color('tabs-color-dark', $dark); 266 | 267 | @mixin tabs-standard-color($style, $color, $off-color:$dark) { 268 | &.#{$style} { 269 | .tab-item { 270 | color: $off-color; 271 | &.tab-item-active, 272 | &.active, 273 | &.activated { 274 | color: $color; 275 | } 276 | } 277 | } 278 | } 279 | 280 | ion-tabs { 281 | @include tabs-standard-color('tabs-color-active-light', $light, $dark); 282 | @include tabs-standard-color('tabs-color-active-stable', $stable, $dark); 283 | @include tabs-standard-color('tabs-color-active-positive', $positive, $dark); 284 | @include tabs-standard-color('tabs-color-active-calm', $calm, $dark); 285 | @include tabs-standard-color('tabs-color-active-assertive', $assertive, $dark); 286 | @include tabs-standard-color('tabs-color-active-balanced', $balanced, $dark); 287 | @include tabs-standard-color('tabs-color-active-energized',$energized, $dark); 288 | @include tabs-standard-color('tabs-color-active-royal', $royal, $dark); 289 | @include tabs-standard-color('tabs-color-active-dark', $dark, $light); 290 | } 291 | 292 | .tabs-top { 293 | &.tabs-striped { 294 | padding-bottom:0; 295 | .tab-item{ 296 | background: transparent; 297 | // animate the top bar, leave bottom for platform consistency 298 | -webkit-transition: all .1s ease; 299 | -moz-transition: all .1s ease; 300 | -ms-transition: all .1s ease; 301 | -o-transition: all .1s ease; 302 | transition: all .1s ease; 303 | &.tab-item-active, 304 | &.active, 305 | &.activated { 306 | margin-top: 0; 307 | margin-bottom: -$tabs-striped-border-width; 308 | border-width: 0px 0px $tabs-striped-border-width 0px !important; 309 | border-style: solid; 310 | } 311 | .badge{ 312 | -webkit-transition: all .2s ease; 313 | -moz-transition: all .2s ease; 314 | -ms-transition: all .2s ease; 315 | -o-transition: all .2s ease; 316 | transition: all .2s ease; 317 | } 318 | } 319 | } 320 | } 321 | 322 | /* Allow parent element to have tabs-top */ 323 | /* If you change this, change platform.scss as well */ 324 | .tabs-top > .tabs, 325 | .tabs.tabs-top { 326 | top: $bar-height; 327 | padding-top: 0; 328 | background-position: bottom; 329 | border-top-width: 0; 330 | border-bottom-width: 1px; 331 | .tab-item { 332 | &.tab-item-active, 333 | &.active, 334 | &.activated { 335 | .badge { 336 | top: 4%; 337 | } 338 | } 339 | } 340 | } 341 | .tabs-top ~ .bar-header { 342 | border-bottom-width: 0; 343 | } 344 | 345 | .tab-item { 346 | @include flex(1); 347 | display: block; 348 | overflow: hidden; 349 | 350 | max-width: $tab-item-max-width; 351 | height: 100%; 352 | 353 | color: inherit; 354 | text-align: center; 355 | text-decoration: none; 356 | text-overflow: ellipsis; 357 | white-space: nowrap; 358 | 359 | font-weight: 400; 360 | font-size: $tabs-text-font-size; 361 | font-family: $font-family-sans-serif; 362 | 363 | opacity: 0.7; 364 | 365 | &:hover { 366 | cursor: pointer; 367 | } 368 | &.tab-hidden{ 369 | display:none; 370 | } 371 | } 372 | 373 | .tabs-item-hide > .tabs, 374 | .tabs.tabs-item-hide { 375 | display: none; 376 | } 377 | 378 | .tabs-icon-top > .tabs .tab-item, 379 | .tabs-icon-top.tabs .tab-item, 380 | .tabs-icon-bottom > .tabs .tab-item, 381 | .tabs-icon-bottom.tabs .tab-item { 382 | font-size: $tabs-text-font-size-side-icon; 383 | line-height: $tabs-text-font-size; 384 | } 385 | 386 | .tab-item .icon { 387 | display: block; 388 | margin: 0 auto; 389 | height: $tabs-icon-size; 390 | font-size: $tabs-icon-size; 391 | } 392 | 393 | .tabs-icon-left.tabs .tab-item, 394 | .tabs-icon-left > .tabs .tab-item, 395 | .tabs-icon-right.tabs .tab-item, 396 | .tabs-icon-right > .tabs .tab-item { 397 | font-size: $tabs-text-font-size-side-icon; 398 | 399 | .icon { 400 | display: inline-block; 401 | vertical-align: top; 402 | margin-top: -.1em; 403 | 404 | &:before { 405 | font-size: $tabs-icon-size - 8; 406 | line-height: $tabs-height; 407 | } 408 | } 409 | } 410 | 411 | .tabs-icon-left > .tabs .tab-item .icon, 412 | .tabs-icon-left.tabs .tab-item .icon { 413 | padding-right: 3px; 414 | } 415 | 416 | .tabs-icon-right > .tabs .tab-item .icon, 417 | .tabs-icon-right.tabs .tab-item .icon { 418 | padding-left: 3px; 419 | } 420 | 421 | .tabs-icon-only > .tabs .icon, 422 | .tabs-icon-only.tabs .icon { 423 | line-height: inherit; 424 | } 425 | 426 | 427 | .tab-item.has-badge { 428 | position: relative; 429 | } 430 | 431 | .tab-item .badge { 432 | position: absolute; 433 | top: 4%; 434 | right: 33%; // fallback 435 | right: calc(50% - 26px); 436 | padding: $tabs-badge-padding; 437 | height: auto; 438 | font-size: $tabs-badge-font-size; 439 | line-height: $tabs-badge-font-size + 4; 440 | } 441 | 442 | 443 | /* Navigational tab */ 444 | 445 | /* Active state for tab */ 446 | .tab-item.tab-item-active, 447 | .tab-item.active, 448 | .tab-item.activated { 449 | opacity: 1; 450 | 451 | &.tab-item-light { 452 | color: $light; 453 | } 454 | &.tab-item-stable { 455 | color: $stable; 456 | } 457 | &.tab-item-positive { 458 | color: $positive; 459 | } 460 | &.tab-item-calm { 461 | color: $calm; 462 | } 463 | &.tab-item-assertive { 464 | color: $assertive; 465 | } 466 | &.tab-item-balanced { 467 | color: $balanced; 468 | } 469 | &.tab-item-energized { 470 | color: $energized; 471 | } 472 | &.tab-item-royal { 473 | color: $royal; 474 | } 475 | &.tab-item-dark { 476 | color: $dark; 477 | } 478 | } 479 | 480 | .item.tabs { 481 | @include display-flex(); 482 | padding: 0; 483 | 484 | .icon:before { 485 | position: relative; 486 | } 487 | } 488 | 489 | .tab-item.disabled, 490 | .tab-item[disabled] { 491 | opacity: .4; 492 | cursor: default; 493 | pointer-events: none; 494 | } 495 | -------------------------------------------------------------------------------- /www/lib/ionic/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | 2 | // Button Mixins 3 | // -------------------------------------------------- 4 | 5 | @mixin button-style($bg-color, $border-color, $active-bg-color, $active-border-color, $color) { 6 | border-color: $border-color; 7 | background-color: $bg-color; 8 | color: $color; 9 | 10 | // Give desktop users something to play with 11 | &:hover { 12 | color: $color; 13 | text-decoration: none; 14 | } 15 | &.active, 16 | &.activated { 17 | border-color: $active-border-color; 18 | background-color: $active-bg-color; 19 | box-shadow: inset 0 1px 4px rgba(0,0,0,0.1); 20 | } 21 | } 22 | 23 | @mixin button-clear($color, $font-size:"") { 24 | &.button-clear { 25 | border-color: transparent; 26 | background: none; 27 | box-shadow: none; 28 | color: $color; 29 | 30 | @if $font-size != "" { 31 | font-size: $font-size; 32 | } 33 | } 34 | &.button-icon { 35 | border-color: transparent; 36 | background: none; 37 | } 38 | } 39 | 40 | @mixin button-outline($color, $text-color:"") { 41 | &.button-outline { 42 | border-color: $color; 43 | background: transparent; 44 | @if $text-color == "" { 45 | $text-color: $color; 46 | } 47 | color: $text-color; 48 | &.active, 49 | &.activated { 50 | background-color: $color; 51 | box-shadow: none; 52 | color: #fff; 53 | } 54 | } 55 | } 56 | 57 | 58 | // Bar Mixins 59 | // -------------------------------------------------- 60 | 61 | @mixin bar-style($bg-color, $border-color, $color) { 62 | border-color: $border-color; 63 | background-color: $bg-color; 64 | background-image: linear-gradient(0deg, $border-color, $border-color 50%, transparent 50%); 65 | color: $color; 66 | 67 | .title { 68 | color: $color; 69 | } 70 | } 71 | 72 | 73 | // Tab Mixins 74 | // -------------------------------------------------- 75 | 76 | @mixin tab-style($bg-color, $border-color, $color) { 77 | border-color: $border-color; 78 | background-color: $bg-color; 79 | background-image: linear-gradient(0deg, $border-color, $border-color 50%, transparent 50%); 80 | color: $color; 81 | } 82 | 83 | @mixin tab-badge-style($bg-color, $color) { 84 | .tab-item .badge { 85 | background-color: $bg-color; 86 | color: $color; 87 | } 88 | } 89 | 90 | 91 | // Item Mixins 92 | // -------------------------------------------------- 93 | 94 | @mixin item-style($bg-color, $border-color, $color) { 95 | border-color: $border-color; 96 | background-color: $bg-color; 97 | color: $color; 98 | } 99 | 100 | @mixin item-active-style($active-bg-color, $active-border-color) { 101 | border-color: $active-border-color; 102 | background-color: $active-bg-color; 103 | } 104 | 105 | 106 | // Badge Mixins 107 | // -------------------------------------------------- 108 | 109 | @mixin badge-style($bg-color, $color) { 110 | background-color: $bg-color; 111 | color: $color; 112 | } 113 | 114 | 115 | // Range Mixins 116 | // -------------------------------------------------- 117 | 118 | @mixin range-style($track-bg-color) { 119 | &::-webkit-slider-thumb:before { 120 | background: $track-bg-color; 121 | } 122 | } 123 | 124 | 125 | // Checkbox Mixins 126 | // -------------------------------------------------- 127 | 128 | @mixin checkbox-style($off-border-color, $on-bg-color, $on-border-color) { 129 | & input:before, 130 | & .checkbox-icon:before { 131 | border-color: $off-border-color; 132 | } 133 | 134 | // what the background looks like when its checked 135 | & input:checked:before, 136 | & input:checked + .checkbox-icon:before { 137 | background: $on-bg-color; 138 | border-color: $on-border-color; 139 | } 140 | } 141 | 142 | 143 | // Toggle Mixins 144 | // -------------------------------------------------- 145 | 146 | @mixin toggle-style($on-border-color, $on-bg-color) { 147 | // the track when the toggle is "on" 148 | & input:checked + .track { 149 | border-color: $on-border-color; 150 | background-color: $on-bg-color; 151 | } 152 | } 153 | 154 | 155 | // Clearfix 156 | // -------------------------------------------------- 157 | 158 | @mixin clearfix { 159 | *zoom: 1; 160 | &:before, 161 | &:after { 162 | display: table; 163 | content: ""; 164 | line-height: 0; 165 | } 166 | &:after { 167 | clear: both; 168 | } 169 | } 170 | 171 | 172 | // Placeholder text 173 | // -------------------------------------------------- 174 | 175 | @mixin placeholder($color: $input-color-placeholder, $text-indent: 0) { 176 | &::-moz-placeholder { // Firefox 19+ 177 | color: $color; 178 | } 179 | &:-ms-input-placeholder { 180 | color: $color; 181 | } 182 | &::-webkit-input-placeholder { 183 | color: $color; 184 | // Safari placeholder margin issue 185 | text-indent: $text-indent; 186 | } 187 | } 188 | 189 | 190 | // Text Mixins 191 | // -------------------------------------------------- 192 | 193 | @mixin text-size-adjust($value: none) { 194 | -webkit-text-size-adjust: $value; 195 | -moz-text-size-adjust: $value; 196 | text-size-adjust: $value; 197 | } 198 | @mixin tap-highlight-transparent() { 199 | -webkit-tap-highlight-color: rgba(0,0,0,0); 200 | -webkit-tap-highlight-color: transparent; // For some Androids 201 | } 202 | @mixin touch-callout($value: none) { 203 | -webkit-touch-callout: $value; 204 | } 205 | 206 | 207 | // Font Mixins 208 | // -------------------------------------------------- 209 | 210 | @mixin font-family-serif() { 211 | font-family: $serif-font-family; 212 | } 213 | @mixin font-family-sans-serif() { 214 | font-family: $sans-font-family; 215 | } 216 | @mixin font-family-monospace() { 217 | font-family: $mono-font-family; 218 | } 219 | @mixin font-shorthand($size: $base-font-size, $weight: normal, $line-height: $base-line-height) { 220 | font-weight: $weight; 221 | font-size: $size; 222 | line-height: $line-height; 223 | } 224 | @mixin font-serif($size: $base-font-size, $weight: normal, $line-height: $base-line-height) { 225 | @include font-family-serif(); 226 | @include font-shorthand($size, $weight, $line-height); 227 | } 228 | @mixin font-sans-serif($size: $base-font-size, $weight: normal, $line-height: $base-line-height) { 229 | @include font-family-sans-serif(); 230 | @include font-shorthand($size, $weight, $line-height); 231 | } 232 | @mixin font-monospace($size: $base-font-size, $weight: normal, $line-height: $base-line-height) { 233 | @include font-family-monospace(); 234 | @include font-shorthand($size, $weight, $line-height); 235 | } 236 | @mixin font-smoothing($font-smoothing) { 237 | -webkit-font-smoothing: $font-smoothing; 238 | font-smoothing: $font-smoothing; 239 | } 240 | 241 | 242 | // Appearance 243 | // -------------------------------------------------- 244 | 245 | @mixin appearance($val) { 246 | -webkit-appearance: $val; 247 | -moz-appearance: $val; 248 | appearance: $val; 249 | } 250 | 251 | 252 | // Border Radius Mixins 253 | // -------------------------------------------------- 254 | 255 | @mixin border-radius($radius) { 256 | -webkit-border-radius: $radius; 257 | border-radius: $radius; 258 | } 259 | 260 | // Single Corner Border Radius 261 | @mixin border-top-left-radius($radius) { 262 | -webkit-border-top-left-radius: $radius; 263 | border-top-left-radius: $radius; 264 | } 265 | @mixin border-top-right-radius($radius) { 266 | -webkit-border-top-right-radius: $radius; 267 | border-top-right-radius: $radius; 268 | } 269 | @mixin border-bottom-right-radius($radius) { 270 | -webkit-border-bottom-right-radius: $radius; 271 | border-bottom-right-radius: $radius; 272 | } 273 | @mixin border-bottom-left-radius($radius) { 274 | -webkit-border-bottom-left-radius: $radius; 275 | border-bottom-left-radius: $radius; 276 | } 277 | 278 | // Single Side Border Radius 279 | @mixin border-top-radius($radius) { 280 | @include border-top-right-radius($radius); 281 | @include border-top-left-radius($radius); 282 | } 283 | @mixin border-right-radius($radius) { 284 | @include border-top-right-radius($radius); 285 | @include border-bottom-right-radius($radius); 286 | } 287 | @mixin border-bottom-radius($radius) { 288 | @include border-bottom-right-radius($radius); 289 | @include border-bottom-left-radius($radius); 290 | } 291 | @mixin border-left-radius($radius) { 292 | @include border-top-left-radius($radius); 293 | @include border-bottom-left-radius($radius); 294 | } 295 | 296 | 297 | // Box shadows 298 | // -------------------------------------------------- 299 | 300 | @mixin box-shadow($shadow...) { 301 | -webkit-box-shadow: $shadow; 302 | box-shadow: $shadow; 303 | } 304 | 305 | 306 | // Transition Mixins 307 | // -------------------------------------------------- 308 | 309 | @mixin transition($transition...) { 310 | -webkit-transition: $transition; 311 | transition: $transition; 312 | } 313 | @mixin transition-delay($transition-delay) { 314 | -webkit-transition-delay: $transition-delay; 315 | transition-delay: $transition-delay; 316 | } 317 | @mixin transition-duration($transition-duration) { 318 | -webkit-transition-duration: $transition-duration; 319 | transition-duration: $transition-duration; 320 | } 321 | @mixin transition-timing-function($transition-timing) { 322 | -webkit-transition-timing-function: $transition-timing; 323 | transition-timing-function: $transition-timing; 324 | } 325 | @mixin transition-property($property) { 326 | -webkit-transition-property: $property; 327 | transition-property: $property; 328 | } 329 | @mixin transition-transform($properties...) { 330 | // special case cuz of transform vendor prefixes 331 | -webkit-transition: -webkit-transform $properties; 332 | transition: transform $properties; 333 | } 334 | 335 | 336 | // Animation Mixins 337 | // -------------------------------------------------- 338 | 339 | @mixin animation($animation) { 340 | -webkit-animation: $animation; 341 | animation: $animation; 342 | } 343 | @mixin animation-duration($duration) { 344 | -webkit-animation-duration: $duration; 345 | animation-duration: $duration; 346 | } 347 | @mixin animation-direction($direction) { 348 | -webkit-animation-direction: $direction; 349 | animation-direction: $direction; 350 | } 351 | @mixin animation-timing-function($animation-timing) { 352 | -webkit-animation-timing-function: $animation-timing; 353 | animation-timing-function: $animation-timing; 354 | } 355 | @mixin animation-fill-mode($fill-mode) { 356 | -webkit-animation-fill-mode: $fill-mode; 357 | animation-fill-mode: $fill-mode; 358 | } 359 | @mixin animation-name($name...) { 360 | -webkit-animation-name: $name; 361 | animation-name: $name; 362 | } 363 | @mixin animation-iteration-count($count) { 364 | -webkit-animation-iteration-count: $count; 365 | animation-iteration-count: $count; 366 | } 367 | 368 | 369 | // Transformation Mixins 370 | // -------------------------------------------------- 371 | 372 | @mixin rotate($degrees) { 373 | @include transform( rotate($degrees) ); 374 | } 375 | @mixin scale($ratio) { 376 | @include transform( scale($ratio) ); 377 | } 378 | @mixin translate($x, $y) { 379 | @include transform( translate($x, $y) ); 380 | } 381 | @mixin skew($x, $y) { 382 | @include transform( skew($x, $y) ); 383 | -webkit-backface-visibility: hidden; 384 | } 385 | @mixin translate3d($x, $y, $z) { 386 | @include transform( translate3d($x, $y, $z) ); 387 | } 388 | @mixin translateZ($z) { 389 | @include transform( translateZ($z) ); 390 | } 391 | @mixin transform($val) { 392 | -webkit-transform: $val; 393 | transform: $val; 394 | } 395 | 396 | @mixin transform-origin($left, $top) { 397 | -webkit-transform-origin: $left $top; 398 | transform-origin: $left $top; 399 | } 400 | 401 | 402 | // Backface visibility 403 | // -------------------------------------------------- 404 | // Prevent browsers from flickering when using CSS 3D transforms. 405 | // Default value is `visible`, but can be changed to `hidden 406 | 407 | @mixin backface-visibility($visibility){ 408 | -webkit-backface-visibility: $visibility; 409 | backface-visibility: $visibility; 410 | } 411 | 412 | 413 | // Background clipping 414 | // -------------------------------------------------- 415 | 416 | @mixin background-clip($clip) { 417 | -webkit-background-clip: $clip; 418 | background-clip: $clip; 419 | } 420 | 421 | 422 | // Background sizing 423 | // -------------------------------------------------- 424 | 425 | @mixin background-size($size) { 426 | -webkit-background-size: $size; 427 | background-size: $size; 428 | } 429 | 430 | 431 | // Box sizing 432 | // -------------------------------------------------- 433 | 434 | @mixin box-sizing($boxmodel) { 435 | -webkit-box-sizing: $boxmodel; 436 | -moz-box-sizing: $boxmodel; 437 | box-sizing: $boxmodel; 438 | } 439 | 440 | 441 | // User select 442 | // -------------------------------------------------- 443 | 444 | @mixin user-select($select) { 445 | -webkit-user-select: $select; 446 | -moz-user-select: $select; 447 | -ms-user-select: $select; 448 | user-select: $select; 449 | } 450 | 451 | 452 | // Content Columns 453 | // -------------------------------------------------- 454 | 455 | @mixin content-columns($columnCount, $columnGap: $grid-gutter-width) { 456 | -webkit-column-count: $columnCount; 457 | -moz-column-count: $columnCount; 458 | column-count: $columnCount; 459 | -webkit-column-gap: $columnGap; 460 | -moz-column-gap: $columnGap; 461 | column-gap: $columnGap; 462 | } 463 | 464 | 465 | // Flexbox Mixins 466 | // -------------------------------------------------- 467 | // http://philipwalton.github.io/solved-by-flexbox/ 468 | // https://github.com/philipwalton/solved-by-flexbox 469 | 470 | @mixin display-flex { 471 | display: -webkit-box; 472 | display: -webkit-flex; 473 | display: -moz-box; 474 | display: -moz-flex; 475 | display: -ms-flexbox; 476 | display: flex; 477 | } 478 | 479 | @mixin display-inline-flex { 480 | display: -webkit-inline-box; 481 | display: -webkit-inline-flex; 482 | display: -moz-inline-flex; 483 | display: -ms-inline-flexbox; 484 | display: inline-flex; 485 | } 486 | 487 | @mixin flex-direction($value: row) { 488 | @if $value == row-reverse { 489 | -webkit-box-direction: reverse; 490 | -webkit-box-orient: horizontal; 491 | } @else if $value == column { 492 | -webkit-box-direction: normal; 493 | -webkit-box-orient: vertical; 494 | } @else if $value == column-reverse { 495 | -webkit-box-direction: reverse; 496 | -webkit-box-orient: vertical; 497 | } @else { 498 | -webkit-box-direction: normal; 499 | -webkit-box-orient: horizontal; 500 | } 501 | -webkit-flex-direction: $value; 502 | -moz-flex-direction: $value; 503 | -ms-flex-direction: $value; 504 | flex-direction: $value; 505 | } 506 | 507 | @mixin flex-wrap($value: nowrap) { 508 | // No Webkit Box fallback. 509 | -webkit-flex-wrap: $value; 510 | -moz-flex-wrap: $value; 511 | @if $value == nowrap { 512 | -ms-flex-wrap: none; 513 | } @else { 514 | -ms-flex-wrap: $value; 515 | } 516 | flex-wrap: $value; 517 | } 518 | 519 | @mixin flex($fg: 1, $fs: null, $fb: null) { 520 | -webkit-box-flex: $fg; 521 | -webkit-flex: $fg $fs $fb; 522 | -moz-box-flex: $fg; 523 | -moz-flex: $fg $fs $fb; 524 | -ms-flex: $fg $fs $fb; 525 | flex: $fg $fs $fb; 526 | } 527 | 528 | @mixin flex-flow($values: (row nowrap)) { 529 | // No Webkit Box fallback. 530 | -webkit-flex-flow: $values; 531 | -moz-flex-flow: $values; 532 | -ms-flex-flow: $values; 533 | flex-flow: $values; 534 | } 535 | 536 | @mixin align-items($value: stretch) { 537 | @if $value == flex-start { 538 | -webkit-box-align: start; 539 | -ms-flex-align: start; 540 | } @else if $value == flex-end { 541 | -webkit-box-align: end; 542 | -ms-flex-align: end; 543 | } @else { 544 | -webkit-box-align: $value; 545 | -ms-flex-align: $value; 546 | } 547 | -webkit-align-items: $value; 548 | -moz-align-items: $value; 549 | align-items: $value; 550 | } 551 | 552 | @mixin align-self($value: auto) { 553 | -webkit-align-self: $value; 554 | -moz-align-self: $value; 555 | @if $value == flex-start { 556 | -ms-flex-item-align: start; 557 | } @else if $value == flex-end { 558 | -ms-flex-item-align: end; 559 | } @else { 560 | -ms-flex-item-align: $value; 561 | } 562 | align-self: $value; 563 | } 564 | 565 | @mixin align-content($value: stretch) { 566 | -webkit-align-content: $value; 567 | -moz-align-content: $value; 568 | @if $value == flex-start { 569 | -ms-flex-line-pack: start; 570 | } @else if $value == flex-end { 571 | -ms-flex-line-pack: end; 572 | } @else { 573 | -ms-flex-line-pack: $value; 574 | } 575 | align-content: $value; 576 | } 577 | 578 | @mixin justify-content($value: stretch) { 579 | @if $value == flex-start { 580 | -webkit-box-pack: start; 581 | -ms-flex-pack: start; 582 | } @else if $value == flex-end { 583 | -webkit-box-pack: end; 584 | -ms-flex-pack: end; 585 | } @else if $value == space-between { 586 | -webkit-box-pack: justify; 587 | -ms-flex-pack: justify; 588 | } @else { 589 | -webkit-box-pack: $value; 590 | -ms-flex-pack: $value; 591 | } 592 | -webkit-justify-content: $value; 593 | -moz-justify-content: $value; 594 | justify-content: $value; 595 | } 596 | 597 | @mixin flex-order($n) { 598 | -webkit-order: $n; 599 | -ms-flex-order: $n; 600 | order: $n; 601 | -webkit-box-ordinal-group: $n; 602 | } 603 | 604 | @mixin responsive-grid-break($selector, $max-width) { 605 | @media (max-width: $max-width) { 606 | #{$selector} { 607 | -webkit-box-direction: normal; 608 | -moz-box-direction: normal; 609 | -webkit-box-orient: vertical; 610 | -moz-box-orient: vertical; 611 | -webkit-flex-direction: column; 612 | -ms-flex-direction: column; 613 | flex-direction: column; 614 | 615 | .col, .col-10, .col-20, .col-25, .col-33, .col-34, .col-50, .col-66, .col-67, .col-75, .col-80, .col-90 { 616 | @include flex(1); 617 | margin-bottom: ($grid-padding-width * 3) / 2; 618 | margin-left: 0; 619 | max-width: 100%; 620 | width: 100%; 621 | } 622 | } 623 | } 624 | } 625 | -------------------------------------------------------------------------------- /www/js/angularfire.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * AngularFire is the officially supported AngularJS binding for Firebase. Firebase 3 | * is a full backend so you don't need servers to build your Angular app. AngularFire 4 | * provides you with the $firebase service which allows you to easily keep your $scope 5 | * variables in sync with your Firebase backend. 6 | * 7 | * AngularFire 1.0.0 8 | * https://github.com/firebase/angularfire/ 9 | * Date: 03/04/2015 10 | * License: MIT 11 | */ 12 | !function(a){"use strict";angular.module("firebase",[]).value("Firebase",a.Firebase).value("firebaseBatchDelay",50)}(window),function(){"use strict";angular.module("firebase").factory("$firebaseArray",["$log","$firebaseUtils",function(a,b){function c(a){if(!(this instanceof c))return new c(a);var e=this;return this._observers=[],this.$list=[],this._ref=a,this._sync=new d(this),b.assertValidRef(a,"Must pass a valid Firebase reference to $firebaseArray (not a string or URL)"),this._indexCache={},b.getPublicMethods(e,function(a,b){e.$list[b]=a.bind(e)}),this._sync.init(this.$list),this.$list}function d(c){function d(a){if(!p.isDestroyed){p.isDestroyed=!0;var b=c.$ref();b.off("child_added",i),b.off("child_moved",k),b.off("child_changed",j),b.off("child_removed",l),c=null,o(a||"destroyed")}}function e(b){var d=c.$ref();d.on("child_added",i,n),d.on("child_moved",k,n),d.on("child_changed",j,n),d.on("child_removed",l,n),d.once("value",function(c){angular.isArray(c.val())&&a.warn("Storing data using array indices in Firebase can result in unexpected behavior. See https://www.firebase.com/docs/web/guide/understanding-data.html#section-arrays-in-firebase for more information."),o(null,b)},o)}function f(a,b){m||(m=!0,a?g.reject(a):g.resolve(b))}var g=b.defer(),h=b.batch(),i=h(function(a,b){var d=c.$$added(a,b);d&&c.$$process("child_added",d,b)}),j=h(function(a){var d=c.$getRecord(b.getKey(a));if(d){var e=c.$$updated(a);e&&c.$$process("child_changed",d)}}),k=h(function(a,d){var e=c.$getRecord(b.getKey(a));if(e){var f=c.$$moved(a,d);f&&c.$$process("child_moved",e,d)}}),l=h(function(a){var d=c.$getRecord(b.getKey(a));if(d){var e=c.$$removed(a);e&&c.$$process("child_removed",d)}}),m=!1,n=h(function(a){f(a),c.$$error(a)}),o=h(f),p={destroy:d,isDestroyed:!1,init:e,ready:function(){return g.promise}};return p}return c.prototype={$add:function(a){this._assertNotDestroyed("$add");var c=b.defer(),d=this.$ref().ref().push();return d.set(b.toJSON(a),b.makeNodeResolver(c)),c.promise.then(function(){return d})},$save:function(a){this._assertNotDestroyed("$save");var c=this,d=c._resolveItem(a),e=c.$keyAt(d);if(null!==e){var f=c.$ref().ref().child(e),g=b.toJSON(d);return b.doSet(f,g).then(function(){return c.$$notify("child_changed",e),f})}return b.reject("Invalid record; could determine key for "+a)},$remove:function(a){this._assertNotDestroyed("$remove");var c=this.$keyAt(a);if(null!==c){var d=this.$ref().ref().child(c);return b.doRemove(d).then(function(){return d})}return b.reject("Invalid record; could not determine key for "+a)},$keyAt:function(a){var b=this._resolveItem(a);return this.$$getKey(b)},$indexFor:function(a){var b=this,c=b._indexCache;if(!c.hasOwnProperty(a)||b.$keyAt(c[a])!==a){var d=b.$list.findIndex(function(c){return b.$$getKey(c)===a});-1!==d&&(c[a]=d)}return c.hasOwnProperty(a)?c[a]:-1},$loaded:function(a,b){var c=this._sync.ready();return arguments.length&&(c=c.then.call(c,a,b)),c},$ref:function(){return this._ref},$watch:function(a,b){var c=this._observers;return c.push([a,b]),function(){var d=c.findIndex(function(c){return c[0]===a&&c[1]===b});d>-1&&c.splice(d,1)}},$destroy:function(b){this._isDestroyed||(this._isDestroyed=!0,this._sync.destroy(b),this.$list.length=0,a.debug("destroy called for FirebaseArray: "+this.$ref().ref().toString()))},$getRecord:function(a){var b=this.$indexFor(a);return b>-1?this.$list[b]:null},$$added:function(a){var c=this.$indexFor(b.getKey(a));if(-1===c){var d=a.val();return angular.isObject(d)||(d={$value:d}),d.$id=b.getKey(a),d.$priority=a.getPriority(),b.applyDefaults(d,this.$$defaults),d}return!1},$$removed:function(a){return this.$indexFor(b.getKey(a))>-1},$$updated:function(a){var c=!1,d=this.$getRecord(b.getKey(a));return angular.isObject(d)&&(c=b.updateRec(d,a),b.applyDefaults(d,this.$$defaults)),c},$$moved:function(a){var c=this.$getRecord(b.getKey(a));return angular.isObject(c)?(c.$priority=a.getPriority(),!0):!1},$$error:function(b){a.error(b),this.$destroy(b)},$$getKey:function(a){return angular.isObject(a)?a.$id:null},$$process:function(a,b,c){var d,e=this.$$getKey(b),f=!1;switch(a){case"child_added":d=this.$indexFor(e);break;case"child_moved":d=this.$indexFor(e),this._spliceOut(e);break;case"child_removed":f=null!==this._spliceOut(e);break;case"child_changed":f=!0;break;default:throw new Error("Invalid event type: "+a)}return angular.isDefined(d)&&(f=this._addAfter(b,c)!==d),f&&this.$$notify(a,e,c),f},$$notify:function(a,b,c){var d={event:a,key:b};angular.isDefined(c)&&(d.prevChild=c),angular.forEach(this._observers,function(a){a[0].call(a[1],d)})},_addAfter:function(a,b){var c;return null===b?c=0:(c=this.$indexFor(b)+1,0===c&&(c=this.$list.length)),this.$list.splice(c,0,a),this._indexCache[this.$$getKey(a)]=c,c},_spliceOut:function(a){var b=this.$indexFor(a);return b>-1?(delete this._indexCache[a],this.$list.splice(b,1)[0]):null},_resolveItem:function(a){var b=this.$list;if(angular.isNumber(a)&&a>=0&&b.length>=a)return b[a];if(angular.isObject(a)){var c=this.$$getKey(a),d=this.$getRecord(c);return d===a?d:null}return null},_assertNotDestroyed:function(a){if(this._isDestroyed)throw new Error("Cannot call "+a+" method on a destroyed $firebaseArray object")}},c.$extend=function(a,d){return 1===arguments.length&&angular.isObject(a)&&(d=a,a=function(){return c.apply(this,arguments)}),b.inherit(a,c,d)},c}]),angular.module("firebase").factory("$FirebaseArray",["$log","$firebaseArray",function(a,b){return function(){return a.warn("$FirebaseArray has been renamed. Use $firebaseArray instead."),b.apply(null,arguments)}}])}(),function(){"use strict";var a;angular.module("firebase").factory("$firebaseAuth",["$q","$firebaseUtils","$log",function(b,c,d){return function(e){var f=new a(b,c,d,e);return f.construct()}}]),a=function(a,b,c,d){if(this._q=a,this._utils=b,this._log=c,"string"==typeof d)throw new Error("Please provide a Firebase reference instead of a URL when creating a `$firebaseAuth` object.");this._ref=d},a.prototype={construct:function(){return this._object={$authWithCustomToken:this.authWithCustomToken.bind(this),$authAnonymously:this.authAnonymously.bind(this),$authWithPassword:this.authWithPassword.bind(this),$authWithOAuthPopup:this.authWithOAuthPopup.bind(this),$authWithOAuthRedirect:this.authWithOAuthRedirect.bind(this),$authWithOAuthToken:this.authWithOAuthToken.bind(this),$unauth:this.unauth.bind(this),$onAuth:this.onAuth.bind(this),$getAuth:this.getAuth.bind(this),$requireAuth:this.requireAuth.bind(this),$waitForAuth:this.waitForAuth.bind(this),$createUser:this.createUser.bind(this),$changePassword:this.changePassword.bind(this),$changeEmail:this.changeEmail.bind(this),$removeUser:this.removeUser.bind(this),$resetPassword:this.resetPassword.bind(this)},this._object},authWithCustomToken:function(a,b){var c=this._q.defer();try{this._ref.authWithCustomToken(a,this._utils.makeNodeResolver(c),b)}catch(d){c.reject(d)}return c.promise},authAnonymously:function(a){var b=this._q.defer();try{this._ref.authAnonymously(this._utils.makeNodeResolver(b),a)}catch(c){b.reject(c)}return b.promise},authWithPassword:function(a,b){var c=this._q.defer();try{this._ref.authWithPassword(a,this._utils.makeNodeResolver(c),b)}catch(d){c.reject(d)}return c.promise},authWithOAuthPopup:function(a,b){var c=this._q.defer();try{this._ref.authWithOAuthPopup(a,this._utils.makeNodeResolver(c),b)}catch(d){c.reject(d)}return c.promise},authWithOAuthRedirect:function(a,b){var c=this._q.defer();try{this._ref.authWithOAuthRedirect(a,this._utils.makeNodeResolver(c),b)}catch(d){c.reject(d)}return c.promise},authWithOAuthToken:function(a,b,c){var d=this._q.defer();try{this._ref.authWithOAuthToken(a,b,this._utils.makeNodeResolver(d),c)}catch(e){d.reject(e)}return d.promise},unauth:function(){null!==this.getAuth()&&this._ref.unauth()},onAuth:function(a,b){var c=this,d=this._utils.debounce(a,b,0);return this._ref.onAuth(d),function(){c._ref.offAuth(d)}},getAuth:function(){return this._ref.getAuth()},_routerMethodOnAuthPromise:function(a){var b=this._ref;return this._utils.promise(function(c,d){function e(f){return b.offAuth(e),null!==f?void c(f):a?void d("AUTH_REQUIRED"):void c(null)}b.onAuth(e)})},requireAuth:function(){return this._routerMethodOnAuthPromise(!0)},waitForAuth:function(){return this._routerMethodOnAuthPromise(!1)},createUser:function(a){var b=this._q.defer();if("string"==typeof a)throw new Error("$createUser() expects an object containing 'email' and 'password', but got a string.");try{this._ref.createUser(a,this._utils.makeNodeResolver(b))}catch(c){b.reject(c)}return b.promise},changePassword:function(a){var b=this._q.defer();if("string"==typeof a)throw new Error("$changePassword() expects an object containing 'email', 'oldPassword', and 'newPassword', but got a string.");try{this._ref.changePassword(a,this._utils.makeNodeResolver(b))}catch(c){b.reject(c)}return b.promise},changeEmail:function(a){if("function"!=typeof this._ref.changeEmail)throw new Error("$changeEmail() expects an object containing 'oldEmail', 'newEmail', and 'password', but got a string.");var b=this._q.defer();try{this._ref.changeEmail(a,this._utils.makeNodeResolver(b))}catch(c){b.reject(c)}return b.promise},removeUser:function(a){var b=this._q.defer();if("string"==typeof a)throw new Error("$removeUser() expects an object containing 'email' and 'password', but got a string.");try{this._ref.removeUser(a,this._utils.makeNodeResolver(b))}catch(c){b.reject(c)}return b.promise},resetPassword:function(a){var b=this._q.defer();if("string"==typeof a)throw new Error("$resetPassword() expects an object containing 'email', but got a string.");try{this._ref.resetPassword(a,this._utils.makeNodeResolver(b))}catch(c){b.reject(c)}return b.promise}}}(),function(){"use strict";angular.module("firebase").factory("$firebaseObject",["$parse","$firebaseUtils","$log",function(a,b,c){function d(a){return this instanceof d?(this.$$conf={sync:new f(this,a),ref:a,binding:new e(this),listeners:[]},Object.defineProperty(this,"$$conf",{value:this.$$conf}),this.$id=b.getKey(a.ref()),this.$priority=null,b.applyDefaults(this,this.$$defaults),void this.$$conf.sync.init()):new d(a)}function e(a){this.subs=[],this.scope=null,this.key=null,this.rec=a}function f(a,d){function e(b){n.isDestroyed||(n.isDestroyed=!0,d.off("value",k),a=null,m(b||"destroyed"))}function f(){d.on("value",k,l),d.once("value",function(a){angular.isArray(a.val())&&c.warn("Storing data using array indices in Firebase can result in unexpected behavior. See https://www.firebase.com/docs/web/guide/understanding-data.html#section-arrays-in-firebase for more information. Also note that you probably wanted $firebaseArray and not $firebaseObject."),m(null)},m)}function g(b){h||(h=!0,b?i.reject(b):i.resolve(a))}var h=!1,i=b.defer(),j=b.batch(),k=j(function(b){var c=a.$$updated(b);c&&a.$$notify()}),l=j(a.$$error,a),m=j(g),n={isDestroyed:!1,destroy:e,init:f,ready:function(){return i.promise}};return n}return d.prototype={$save:function(){var a=this,c=a.$ref(),d=b.toJSON(a);return b.doSet(c,d).then(function(){return a.$$notify(),a.$ref()})},$remove:function(){var a=this;return b.trimKeys(a,{}),a.$value=null,b.doRemove(a.$ref()).then(function(){return a.$$notify(),a.$ref()})},$loaded:function(a,b){var c=this.$$conf.sync.ready();return arguments.length&&(c=c.then.call(c,a,b)),c},$ref:function(){return this.$$conf.ref},$bindTo:function(a,b){var c=this;return c.$loaded().then(function(){return c.$$conf.binding.bindTo(a,b)})},$watch:function(a,b){var c=this.$$conf.listeners;return c.push([a,b]),function(){var d=c.findIndex(function(c){return c[0]===a&&c[1]===b});d>-1&&c.splice(d,1)}},$destroy:function(a){var c=this;c.$isDestroyed||(c.$isDestroyed=!0,c.$$conf.sync.destroy(a),c.$$conf.binding.destroy(),b.each(c,function(a,b){delete c[b]}))},$$updated:function(a){var c=b.updateRec(this,a);return b.applyDefaults(this,this.$$defaults),c},$$error:function(a){c.error(a),this.$destroy(a)},$$scopeUpdated:function(a){var c=b.defer();return this.$ref().set(b.toJSON(a),b.makeNodeResolver(c)),c.promise},$$notify:function(){var a=this,b=this.$$conf.listeners.slice();angular.forEach(b,function(b){b[0].call(b[1],{event:"value",key:a.$id})})},forEach:function(a,c){return b.each(this,a,c)}},d.$extend=function(a,c){return 1===arguments.length&&angular.isObject(a)&&(c=a,a=function(){d.apply(this,arguments)}),b.inherit(a,d,c)},e.prototype={assertNotBound:function(a){if(this.scope){var d="Cannot bind to "+a+" because this instance is already bound to "+this.key+"; one binding per instance (call unbind method or create another FirebaseObject instance)";return c.error(d),b.reject(d)}},bindTo:function(c,d){function e(e){function f(a){return angular.equals(a,k)&&a.$priority===k.$priority&&a.$value===k.$value}function g(a){j.assign(c,b.scopeData(a))}function h(){var a=j(c);return[a,a.$priority,a.$value]}var i=!1,j=a(d),k=e.rec;e.scope=c,e.varName=d;var l=b.debounce(function(a){var d=b.scopeData(a);k.$$scopeUpdated(d)["finally"](function(){i=!1,d.hasOwnProperty("$value")||(delete k.$value,delete j(c).$value)})},50,500),m=function(a){a=a[0],f(a)||(i=!0,l(a))},n=function(){i||f(j(c))||g(k)};return g(k),e.subs.push(c.$on("$destroy",e.unbind.bind(e))),e.subs.push(c.$watch(h,m,!0)),e.subs.push(k.$watch(n)),e.unbind.bind(e)}return this.assertNotBound(d)||e(this)},unbind:function(){this.scope&&(angular.forEach(this.subs,function(a){a()}),this.subs=[],this.scope=null,this.key=null)},destroy:function(){this.unbind(),this.rec=null}},d}]),angular.module("firebase").factory("$FirebaseObject",["$log","$firebaseObject",function(a,b){return function(){return a.warn("$FirebaseObject has been renamed. Use $firebaseObject instead."),b.apply(null,arguments)}}])}(),function(){"use strict";angular.module("firebase").factory("$firebase",function(){return function(){throw new Error("$firebase has been removed. You may instantiate $firebaseArray and $firebaseObject directly now. For simple write operations, just use the Firebase ref directly. See the AngularFire 1.0.0 changelog for details: https://www.firebase.com/docs/web/libraries/angular/changelog.html")}})}(),Array.prototype.indexOf||(Array.prototype.indexOf=function(a,b){if(void 0===this||null===this)throw new TypeError("'this' is null or not defined");var c=this.length>>>0;for(b=+b||0,1/0===Math.abs(b)&&(b=0),0>b&&(b+=c,0>b&&(b=0));c>b;b++)if(this[b]===a)return b;return-1}),Function.prototype.bind||(Function.prototype.bind=function(a){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var b=Array.prototype.slice.call(arguments,1),c=this,d=function(){},e=function(){return c.apply(this instanceof d&&a?this:a,b.concat(Array.prototype.slice.call(arguments)))};return d.prototype=this.prototype,e.prototype=new d,e}),Array.prototype.findIndex||Object.defineProperty(Array.prototype,"findIndex",{enumerable:!1,configurable:!0,writable:!0,value:function(a){if(null==this)throw new TypeError("Array.prototype.find called on null or undefined");if("function"!=typeof a)throw new TypeError("predicate must be a function");for(var b,c=Object(this),d=c.length>>>0,e=arguments[1],f=0;d>f;f++)if(f in c&&(b=c[f],a.call(e,b,f,c)))return f;return-1}}),"function"!=typeof Object.create&&!function(){var a=function(){};Object.create=function(b){if(arguments.length>1)throw new Error("Second argument not supported");if(null===b)throw new Error("Cannot set a null [[Prototype]]");if("object"!=typeof b)throw new TypeError("Argument must be an object");return a.prototype=b,new a}}(),Object.keys||(Object.keys=function(){"use strict";var a=Object.prototype.hasOwnProperty,b=!{toString:null}.propertyIsEnumerable("toString"),c=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],d=c.length;return function(e){if("object"!=typeof e&&("function"!=typeof e||null===e))throw new TypeError("Object.keys called on non-object");var f,g,h=[];for(f in e)a.call(e,f)&&h.push(f);if(b)for(g=0;d>g;g++)a.call(e,c[g])&&h.push(c[g]);return h}}()),"function"!=typeof Object.getPrototypeOf&&(Object.getPrototypeOf="object"==typeof"test".__proto__?function(a){return a.__proto__}:function(a){return a.constructor.prototype}),function(){"use strict";function a(b){if(!angular.isObject(b))return b;var c=angular.isArray(b)?[]:{};return angular.forEach(b,function(b,d){("string"!=typeof d||"$"!==d.charAt(0))&&(c[d]=a(b))}),c}angular.module("firebase").factory("$firebaseConfig",["$firebaseArray","$firebaseObject","$injector",function(a,b,c){return function(d){var e=angular.extend({},d);return"string"==typeof e.objectFactory&&(e.objectFactory=c.get(e.objectFactory)),"string"==typeof e.arrayFactory&&(e.arrayFactory=c.get(e.arrayFactory)),angular.extend({arrayFactory:a,objectFactory:b},e)}}]).factory("$firebaseUtils",["$q","$timeout","firebaseBatchDelay",function(b,c,d){function e(a){function c(a){e.resolve(a)}function d(a){e.reject(a)}if(!angular.isFunction(a))throw new Error("missing resolver function");var e=b.defer();return a(c,d),e.promise}var f={batch:function(a,b){function c(a,b){if("function"!=typeof a)throw new Error("Must provide a function to be batched. Got "+a);return function(){var c=Array.prototype.slice.call(arguments,0);k.push([a,b,c]),e()}}function e(){i&&(i(),i=null),h&&Date.now()-h>b?j||(j=!0,f.compile(g)):(h||(h=Date.now()),i=f.wait(g,a))}function g(){i=null,h=null,j=!1;var a=k.slice(0);k=[],angular.forEach(a,function(a){a[0].apply(a[1],a[2])})}a=d,b||(b=10*a||100);var h,i,j,k=[];return c},debounce:function(a,b,c,d){function e(){j&&(j(),j=null),i&&Date.now()-i>d?l||(l=!0,f.compile(g)):(i||(i=Date.now()),j=f.wait(g,c))}function g(){j=null,i=null,l=!1,a.apply(b,k)}function h(){k=Array.prototype.slice.call(arguments,0),e()}var i,j,k,l;if("number"==typeof b&&(d=c,c=b,b=null),"number"!=typeof c)throw new Error("Must provide a valid integer for wait. Try 0 for a default");if("function"!=typeof a)throw new Error("Must provide a valid function to debounce");return d||(d=10*c||100),h.running=function(){return i>0},h},assertValidRef:function(a,b){if(!angular.isObject(a)||"function"!=typeof a.ref||"function"!=typeof a.ref().transaction)throw new Error(b||"Invalid Firebase reference")},inherit:function(a,b,c){var d=a.prototype;return a.prototype=Object.create(b.prototype),a.prototype.constructor=a,angular.forEach(Object.keys(d),function(b){a.prototype[b]=d[b]}),angular.isObject(c)&&angular.extend(a.prototype,c),a},getPrototypeMethods:function(a,b,c){for(var d={},e=Object.getPrototypeOf({}),f=angular.isFunction(a)&&angular.isObject(a.prototype)?a.prototype:Object.getPrototypeOf(a);f&&f!==e;){for(var g in f)f.hasOwnProperty(g)&&!d.hasOwnProperty(g)&&(d[g]=!0,b.call(c,f[g],g,f));f=Object.getPrototypeOf(f)}},getPublicMethods:function(a,b,c){f.getPrototypeMethods(a,function(a,d){"function"==typeof a&&"_"!==d.charAt(0)&&b.call(c,a,d)})},defer:b.defer,reject:b.reject,resolve:b.when,promise:angular.isFunction(b)?b:e,makeNodeResolver:function(a){return function(b,c){null===b?(arguments.length>2&&(c=Array.prototype.slice.call(arguments,1)),a.resolve(c)):a.reject(b)}},wait:function(a,b){var d=c(a,b||0);return function(){d&&(c.cancel(d),d=null)}},compile:function(a){return c(a||function(){})},deepCopy:function(a){if(!angular.isObject(a))return a;var b=angular.isArray(a)?a.slice():angular.extend({},a);for(var c in b)b.hasOwnProperty(c)&&angular.isObject(b[c])&&(b[c]=f.deepCopy(b[c]));return b},trimKeys:function(a,b){f.each(a,function(c,d){b.hasOwnProperty(d)||delete a[d]})},scopeData:function(a){var b={$id:a.$id,$priority:a.$priority},c=!1;return f.each(a,function(a,d){c=!0,b[d]=f.deepCopy(a)}),!c&&a.hasOwnProperty("$value")&&(b.$value=a.$value),b},updateRec:function(a,b){var c=b.val(),d=angular.extend({},a);return angular.isObject(c)?delete a.$value:(a.$value=c,c={}),f.trimKeys(a,c),angular.extend(a,c),a.$priority=b.getPriority(),!angular.equals(d,a)||d.$value!==a.$value||d.$priority!==a.$priority},applyDefaults:function(a,b){return angular.isObject(b)&&angular.forEach(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)}),a},dataKeys:function(a){var b=[];return f.each(a,function(a,c){b.push(c)}),b},each:function(a,b,c){if(angular.isObject(a)){for(var d in a)if(a.hasOwnProperty(d)){var e=d.charAt(0);"_"!==e&&"$"!==e&&"."!==e&&b.call(c,a[d],d,a)}}else if(angular.isArray(a))for(var f=0,g=a.length;g>f;f++)b.call(c,a[f],f,a);return a},getKey:function(a){return"function"==typeof a.key?a.key():a.name()},toJSON:function(b){var c;return angular.isObject(b)||(b={$value:b}),angular.isFunction(b.toJSON)?c=b.toJSON():(c={},f.each(b,function(b,d){c[d]=a(b)})),angular.isDefined(b.$value)&&0===Object.keys(c).length&&null!==b.$value&&(c[".value"]=b.$value),angular.isDefined(b.$priority)&&Object.keys(c).length>0&&null!==b.$priority&&(c[".priority"]=b.$priority),angular.forEach(c,function(a,b){if(b.match(/[.$\[\]#\/]/)&&".value"!==b&&".priority"!==b)throw new Error("Invalid key "+b+" (cannot contain .$[]#)");if(angular.isUndefined(a))throw new Error("Key "+b+" was undefined. Cannot pass undefined in JSON. Use null instead.")}),c},doSet:function(a,b){var c=f.defer();if(angular.isFunction(a.set)||!angular.isObject(b))a.set(b,f.makeNodeResolver(c));else{var d=angular.extend({},b);a.once("value",function(b){b.forEach(function(a){d.hasOwnProperty(f.getKey(a))||(d[f.getKey(a)]=null)}),a.ref().update(d,f.makeNodeResolver(c))},function(a){c.reject(a)})}return c.promise},doRemove:function(a){var b=f.defer();return angular.isFunction(a.remove)?a.remove(f.makeNodeResolver(b)):a.once("value",function(c){var d=[];c.forEach(function(a){var c=f.defer();d.push(c.promise),a.ref().remove(f.makeNodeResolver(b))}),f.allPromises(d).then(function(){b.resolve(a)},function(a){b.reject(a)})},function(a){b.reject(a)}),b.promise},VERSION:"1.0.0",batchDelay:d,allPromises:b.all.bind(b)};return f}])}(); --------------------------------------------------------------------------------